00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030 #include <OgreColourValue.h>
00031 #include <OgreMatrix4.h>
00032 #include <OgreVector3.h>
00033
00034 #include "rviz/properties/bool_property.h"
00035 #include "rviz/properties/color_property.h"
00036 #include "rviz/properties/editable_enum_property.h"
00037 #include "rviz/properties/enum_property.h"
00038 #include "rviz/properties/float_property.h"
00039 #include "rviz/validate_floats.h"
00040
00041 #include "point_cloud_transformers.h"
00042
00043 namespace rviz
00044 {
00045
00046 static void getRainbowColor(float value, Ogre::ColourValue& color)
00047 {
00048
00049
00050 value = std::min(value, 1.0f);
00051 value = std::max(value, 0.0f);
00052
00053 float h = value * 5.0f + 1.0f;
00054 int i = floor(h);
00055 float f = h - i;
00056 if ( !(i&1) ) f = 1 - f;
00057 float n = 1 - f;
00058
00059 if (i <= 1) color[0] = n, color[1] = 0, color[2] = 1;
00060 else if (i == 2) color[0] = 0, color[1] = n, color[2] = 1;
00061 else if (i == 3) color[0] = 0, color[1] = 1, color[2] = n;
00062 else if (i == 4) color[0] = n, color[1] = 1, color[2] = 0;
00063 else if (i >= 5) color[0] = 1, color[1] = n, color[2] = 0;
00064 }
00065
00066 uint8_t IntensityPCTransformer::supports(const sensor_msgs::PointCloud2ConstPtr& cloud)
00067 {
00068 updateChannels(cloud);
00069 return Support_Color;
00070 }
00071
00072 uint8_t IntensityPCTransformer::score(const sensor_msgs::PointCloud2ConstPtr& cloud)
00073 {
00074 return 255;
00075 }
00076
00077 bool IntensityPCTransformer::transform( const sensor_msgs::PointCloud2ConstPtr& cloud,
00078 uint32_t mask,
00079 const Ogre::Matrix4& transform,
00080 V_PointCloudPoint& points_out )
00081 {
00082 if( !( mask & Support_Color ))
00083 {
00084 return false;
00085 }
00086
00087 int32_t index = findChannelIndex( cloud, channel_name_property_->getStdString() );
00088
00089 if( index == -1 )
00090 {
00091 if( channel_name_property_->getStdString() == "intensity" )
00092 {
00093 index = findChannelIndex( cloud, "intensities" );
00094 if( index == -1 )
00095 {
00096 return false;
00097 }
00098 }
00099 else
00100 {
00101 return false;
00102 }
00103 }
00104
00105 const uint32_t offset = cloud->fields[index].offset;
00106 const uint8_t type = cloud->fields[index].datatype;
00107 const uint32_t point_step = cloud->point_step;
00108 const uint32_t num_points = cloud->width * cloud->height;
00109
00110 float min_intensity = 999999.0f;
00111 float max_intensity = -999999.0f;
00112 if( auto_compute_intensity_bounds_property_->getBool() )
00113 {
00114 for( uint32_t i = 0; i < num_points; ++i )
00115 {
00116 float val = valueFromCloud<float>(cloud, offset, type, point_step, i);
00117 min_intensity = std::min(val, min_intensity);
00118 max_intensity = std::max(val, max_intensity);
00119 }
00120
00121 min_intensity = std::max(-999999.0f, min_intensity);
00122 max_intensity = std::min(999999.0f, max_intensity);
00123 min_intensity_property_->setFloat( min_intensity );
00124 max_intensity_property_->setFloat( max_intensity );
00125 }
00126 else
00127 {
00128 min_intensity = min_intensity_property_->getFloat();
00129 max_intensity = max_intensity_property_->getFloat();
00130 }
00131 float diff_intensity = max_intensity - min_intensity;
00132 if( diff_intensity == 0 )
00133 {
00134
00135
00136
00137
00138 diff_intensity = 1e20;
00139 }
00140 Ogre::ColourValue max_color = max_color_property_->getOgreColor();
00141 Ogre::ColourValue min_color = min_color_property_->getOgreColor();
00142
00143 if( use_rainbow_property_->getBool() )
00144 {
00145 for (uint32_t i = 0; i < num_points; ++i)
00146 {
00147 float val = valueFromCloud<float>(cloud, offset, type, point_step, i);
00148 float value = 1.0 - (val - min_intensity)/diff_intensity;
00149 if(invert_rainbow_property_->getBool() ){
00150 value = 1.0 - value;
00151 }
00152 getRainbowColor(value, points_out[i].color);
00153 }
00154 }
00155 else
00156 {
00157 for (uint32_t i = 0; i < num_points; ++i)
00158 {
00159 float val = valueFromCloud<float>(cloud, offset, type, point_step, i);
00160 float normalized_intensity = ( val - min_intensity ) / diff_intensity;
00161 normalized_intensity = std::min(1.0f, std::max(0.0f, normalized_intensity));
00162 points_out[i].color.r = max_color.r * normalized_intensity + min_color.r * (1.0f - normalized_intensity);
00163 points_out[i].color.g = max_color.g * normalized_intensity + min_color.g * (1.0f - normalized_intensity);
00164 points_out[i].color.b = max_color.b * normalized_intensity + min_color.b * (1.0f - normalized_intensity);
00165 }
00166 }
00167
00168 return true;
00169 }
00170
00171 void IntensityPCTransformer::createProperties( Property* parent_property, uint32_t mask, QList<Property*>& out_props )
00172 {
00173 if( mask & Support_Color )
00174 {
00175 channel_name_property_ = new EditableEnumProperty( "Channel Name", "intensity",
00176 "Select the channel to use to compute the intensity",
00177 parent_property, SIGNAL( needRetransform() ), this );
00178
00179 use_rainbow_property_ = new BoolProperty( "Use rainbow", true,
00180 "Whether to use a rainbow of colors or interpolate between two",
00181 parent_property, SLOT( updateUseRainbow() ), this );
00182 invert_rainbow_property_ = new BoolProperty( "Invert Rainbow", false,
00183 "Whether to invert rainbow colors",
00184 parent_property, SLOT( updateUseRainbow() ), this );
00185
00186 min_color_property_ = new ColorProperty( "Min Color", Qt::black,
00187 "Color to assign the points with the minimum intensity. "
00188 "Actual color is interpolated between this and Max Color.",
00189 parent_property, SIGNAL( needRetransform() ), this );
00190
00191 max_color_property_ = new ColorProperty( "Max Color", Qt::white,
00192 "Color to assign the points with the maximum intensity. "
00193 "Actual color is interpolated between this and Min Color.",
00194 parent_property, SIGNAL( needRetransform() ), this );
00195
00196 auto_compute_intensity_bounds_property_ = new BoolProperty( "Autocompute Intensity Bounds", true,
00197 "Whether to automatically compute the intensity min/max values.",
00198 parent_property, SLOT( updateAutoComputeIntensityBounds() ), this );
00199
00200 min_intensity_property_ = new FloatProperty( "Min Intensity", 0,
00201 "Minimum possible intensity value, used to interpolate from Min Color to Max Color for a point.",
00202 parent_property );
00203
00204 max_intensity_property_ = new FloatProperty( "Max Intensity", 4096,
00205 "Maximum possible intensity value, used to interpolate from Min Color to Max Color for a point.",
00206 parent_property );
00207
00208 out_props.push_back( channel_name_property_ );
00209 out_props.push_back( use_rainbow_property_ );
00210 out_props.push_back( invert_rainbow_property_ );
00211 out_props.push_back( min_color_property_ );
00212 out_props.push_back( max_color_property_ );
00213 out_props.push_back( auto_compute_intensity_bounds_property_ );
00214 out_props.push_back( min_intensity_property_ );
00215 out_props.push_back( max_intensity_property_ );
00216
00217 updateUseRainbow();
00218 updateAutoComputeIntensityBounds();
00219 }
00220 }
00221
00222 void IntensityPCTransformer::updateChannels( const sensor_msgs::PointCloud2ConstPtr& cloud )
00223 {
00224 V_string channels;
00225 for(size_t i = 0; i < cloud->fields.size(); ++i )
00226 {
00227 channels.push_back(cloud->fields[i].name );
00228 }
00229 std::sort(channels.begin(), channels.end());
00230
00231 if( channels != available_channels_ )
00232 {
00233 channel_name_property_->clearOptions();
00234 for( V_string::const_iterator it = channels.begin(); it != channels.end(); ++it )
00235 {
00236 const std::string& channel = *it;
00237 if( channel.empty() )
00238 {
00239 continue;
00240 }
00241 channel_name_property_->addOptionStd( channel );
00242 }
00243 available_channels_ = channels;
00244 }
00245 }
00246
00247 void IntensityPCTransformer::updateAutoComputeIntensityBounds()
00248 {
00249 bool auto_compute = auto_compute_intensity_bounds_property_->getBool();
00250 min_intensity_property_->setHidden( auto_compute );
00251 max_intensity_property_->setHidden( auto_compute );
00252 if( auto_compute )
00253 {
00254 disconnect( min_intensity_property_, SIGNAL( changed() ), this, SIGNAL( needRetransform() ));
00255 disconnect( max_intensity_property_, SIGNAL( changed() ), this, SIGNAL( needRetransform() ));
00256 }
00257 else
00258 {
00259 connect( min_intensity_property_, SIGNAL( changed() ), this, SIGNAL( needRetransform() ));
00260 connect( max_intensity_property_, SIGNAL( changed() ), this, SIGNAL( needRetransform() ));
00261 }
00262 Q_EMIT needRetransform();
00263 }
00264
00265 void IntensityPCTransformer::updateUseRainbow()
00266 {
00267 bool use_rainbow = use_rainbow_property_->getBool();
00268 invert_rainbow_property_->setHidden( !use_rainbow );
00269 min_color_property_->setHidden( use_rainbow );
00270 max_color_property_->setHidden( use_rainbow );
00271 Q_EMIT needRetransform();
00272 }
00273
00274 uint8_t XYZPCTransformer::supports(const sensor_msgs::PointCloud2ConstPtr& cloud)
00275 {
00276 int32_t xi = findChannelIndex(cloud, "x");
00277 int32_t yi = findChannelIndex(cloud, "y");
00278 int32_t zi = findChannelIndex(cloud, "z");
00279
00280 if (xi == -1 || yi == -1 || zi == -1)
00281 {
00282 return Support_None;
00283 }
00284
00285 if (cloud->fields[xi].datatype == sensor_msgs::PointField::FLOAT32)
00286 {
00287 return Support_XYZ;
00288 }
00289
00290 return Support_None;
00291 }
00292
00293 bool XYZPCTransformer::transform(const sensor_msgs::PointCloud2ConstPtr& cloud, uint32_t mask, const Ogre::Matrix4& transform, V_PointCloudPoint& points_out)
00294 {
00295 if (!(mask & Support_XYZ))
00296 {
00297 return false;
00298 }
00299
00300 int32_t xi = findChannelIndex(cloud, "x");
00301 int32_t yi = findChannelIndex(cloud, "y");
00302 int32_t zi = findChannelIndex(cloud, "z");
00303
00304 const uint32_t xoff = cloud->fields[xi].offset;
00305 const uint32_t yoff = cloud->fields[yi].offset;
00306 const uint32_t zoff = cloud->fields[zi].offset;
00307 const uint32_t point_step = cloud->point_step;
00308 const uint32_t num_points = cloud->width * cloud->height;
00309 uint8_t const* point_x = &cloud->data.front() + xoff;
00310 uint8_t const* point_y = &cloud->data.front() + yoff;
00311 uint8_t const* point_z = &cloud->data.front() + zoff;
00312 for (V_PointCloudPoint::iterator iter = points_out.begin(); iter != points_out.end(); ++iter, point_x += point_step,
00313 point_y += point_step, point_z += point_step)
00314 {
00315 iter->position.x = *reinterpret_cast<const float*>(point_x);
00316 iter->position.y = *reinterpret_cast<const float*>(point_y);
00317 iter->position.z = *reinterpret_cast<const float*>(point_z);
00318 }
00319
00320 return true;
00321 }
00322
00323 uint8_t RGB8PCTransformer::supports(const sensor_msgs::PointCloud2ConstPtr& cloud)
00324 {
00325 int32_t index = std::max(findChannelIndex(cloud, "rgb"), findChannelIndex(cloud, "rgba"));
00326 if (index == -1)
00327 {
00328 return Support_None;
00329 }
00330
00331 if (cloud->fields[index].datatype == sensor_msgs::PointField::INT32 ||
00332 cloud->fields[index].datatype == sensor_msgs::PointField::UINT32 ||
00333 cloud->fields[index].datatype == sensor_msgs::PointField::FLOAT32)
00334 {
00335 return Support_Color;
00336 }
00337
00338 return Support_None;
00339 }
00340
00341 bool RGB8PCTransformer::transform(const sensor_msgs::PointCloud2ConstPtr& cloud, uint32_t mask, const Ogre::Matrix4& transform, V_PointCloudPoint& points_out)
00342 {
00343 if (!(mask & Support_Color))
00344 {
00345 return false;
00346 }
00347
00348 const int32_t rgb = findChannelIndex(cloud, "rgb");
00349 const int32_t rgba = findChannelIndex(cloud, "rgba");
00350 const int32_t index = std::max(rgb, rgba);
00351
00352 const uint32_t off = cloud->fields[index].offset;
00353 uint8_t const* rgb_ptr = &cloud->data.front() + off;
00354 const uint32_t point_step = cloud->point_step;
00355
00356
00357 float rgb_lut[256];
00358 for(int i = 0; i < 256; ++i)
00359 {
00360 rgb_lut[i] = float(i)/255.0f;
00361 }
00362 if (rgb != -1)
00363 {
00364 for (V_PointCloudPoint::iterator iter = points_out.begin(); iter != points_out.end(); ++iter, rgb_ptr += point_step)
00365 {
00366 uint32_t rgb = *reinterpret_cast<const uint32_t*>(rgb_ptr);
00367 iter->color.r = rgb_lut[(rgb >> 16) & 0xff];
00368 iter->color.g = rgb_lut[(rgb >> 8) & 0xff];
00369 iter->color.b = rgb_lut[rgb & 0xff];
00370 iter->color.a = 1.0f;
00371 }
00372 }
00373 else
00374 {
00375 for (V_PointCloudPoint::iterator iter = points_out.begin(); iter != points_out.end(); ++iter, rgb_ptr += point_step)
00376 {
00377 uint32_t rgb = *reinterpret_cast<const uint32_t*>(rgb_ptr);
00378 iter->color.r = rgb_lut[(rgb >> 16) & 0xff];
00379 iter->color.g = rgb_lut[(rgb >> 8) & 0xff];
00380 iter->color.b = rgb_lut[rgb & 0xff];
00381 iter->color.a = rgb_lut[rgb >> 24];
00382 }
00383 }
00384
00385 return true;
00386 }
00387
00388 uint8_t RGBF32PCTransformer::supports(const sensor_msgs::PointCloud2ConstPtr& cloud)
00389 {
00390 int32_t ri = findChannelIndex(cloud, "r");
00391 int32_t gi = findChannelIndex(cloud, "g");
00392 int32_t bi = findChannelIndex(cloud, "b");
00393 if (ri == -1 || gi == -1 || bi == -1)
00394 {
00395 return Support_None;
00396 }
00397
00398 if (cloud->fields[ri].datatype == sensor_msgs::PointField::FLOAT32)
00399 {
00400 return Support_Color;
00401 }
00402
00403 return Support_None;
00404 }
00405
00406 bool RGBF32PCTransformer::transform(const sensor_msgs::PointCloud2ConstPtr& cloud, uint32_t mask, const Ogre::Matrix4& transform, V_PointCloudPoint& points_out)
00407 {
00408 if (!(mask & Support_Color))
00409 {
00410 return false;
00411 }
00412
00413 int32_t ri = findChannelIndex(cloud, "r");
00414 int32_t gi = findChannelIndex(cloud, "g");
00415 int32_t bi = findChannelIndex(cloud, "b");
00416
00417 const uint32_t roff = cloud->fields[ri].offset;
00418 const uint32_t goff = cloud->fields[gi].offset;
00419 const uint32_t boff = cloud->fields[bi].offset;
00420 const uint32_t point_step = cloud->point_step;
00421 const uint32_t num_points = cloud->width * cloud->height;
00422 uint8_t const* point = &cloud->data.front();
00423 for (uint32_t i = 0; i < num_points; ++i, point += point_step)
00424 {
00425 float r = *reinterpret_cast<const float*>(point + roff);
00426 float g = *reinterpret_cast<const float*>(point + goff);
00427 float b = *reinterpret_cast<const float*>(point + boff);
00428 points_out[i].color = Ogre::ColourValue(r, g, b);
00429 }
00430
00431 return true;
00432 }
00433
00434 uint8_t FlatColorPCTransformer::supports(const sensor_msgs::PointCloud2ConstPtr& cloud)
00435 {
00436 return Support_Color;
00437 }
00438
00439 uint8_t FlatColorPCTransformer::score(const sensor_msgs::PointCloud2ConstPtr& cloud)
00440 {
00441 return 0;
00442 }
00443
00444 bool FlatColorPCTransformer::transform( const sensor_msgs::PointCloud2ConstPtr& cloud,
00445 uint32_t mask,
00446 const Ogre::Matrix4& transform,
00447 V_PointCloudPoint& points_out )
00448 {
00449 if( !( mask & Support_Color ))
00450 {
00451 return false;
00452 }
00453
00454 Ogre::ColourValue color = color_property_->getOgreColor();
00455
00456 const uint32_t num_points = cloud->width * cloud->height;
00457 for( uint32_t i = 0; i < num_points; ++i )
00458 {
00459 points_out[i].color = color;
00460 }
00461
00462 return true;
00463 }
00464
00465 void FlatColorPCTransformer::createProperties( Property* parent_property, uint32_t mask, QList<Property*>& out_props )
00466 {
00467 if( mask & Support_Color )
00468 {
00469 color_property_ = new ColorProperty( "Color", Qt::white,
00470 "Color to assign to every point.",
00471 parent_property, SIGNAL( needRetransform() ), this );
00472 out_props.push_back( color_property_ );
00473 }
00474 }
00475
00476 uint8_t AxisColorPCTransformer::supports(const sensor_msgs::PointCloud2ConstPtr& cloud)
00477 {
00478 return Support_Color;
00479 }
00480
00481 uint8_t AxisColorPCTransformer::score(const sensor_msgs::PointCloud2ConstPtr& cloud)
00482 {
00483 return 255;
00484 }
00485
00486 bool AxisColorPCTransformer::transform( const sensor_msgs::PointCloud2ConstPtr& cloud,
00487 uint32_t mask,
00488 const Ogre::Matrix4& transform,
00489 V_PointCloudPoint& points_out )
00490 {
00491 if( !( mask & Support_Color ))
00492 {
00493 return false;
00494 }
00495
00496 int32_t xi = findChannelIndex( cloud, "x" );
00497 int32_t yi = findChannelIndex( cloud, "y" );
00498 int32_t zi = findChannelIndex( cloud, "z" );
00499
00500 const uint32_t xoff = cloud->fields[xi].offset;
00501 const uint32_t yoff = cloud->fields[yi].offset;
00502 const uint32_t zoff = cloud->fields[zi].offset;
00503 const uint32_t point_step = cloud->point_step;
00504 const uint32_t num_points = cloud->width * cloud->height;
00505 uint8_t const* point = &cloud->data.front();
00506
00507
00508 int axis = axis_property_->getOptionInt();
00509 std::vector<float> values;
00510 values.reserve( num_points );
00511 Ogre::Vector3 pos;
00512 if( use_fixed_frame_property_->getBool() )
00513 {
00514 for (uint32_t i = 0; i < num_points; ++i, point += point_step)
00515 {
00516
00517
00518
00519 pos.x = *reinterpret_cast<const float*>(point + xoff);
00520 pos.y = *reinterpret_cast<const float*>(point + yoff);
00521 pos.z = *reinterpret_cast<const float*>(point + zoff);
00522
00523 pos = transform * pos;
00524 values.push_back( pos[ axis ]);
00525 }
00526 }
00527 else
00528 {
00529 const uint32_t offsets[ 3 ] = { xoff, yoff, zoff };
00530 const uint32_t off = offsets[ axis ];
00531 for (uint32_t i = 0; i < num_points; ++i, point += point_step)
00532 {
00533 values.push_back( *reinterpret_cast<const float*>( point + off ));
00534 }
00535 }
00536 float min_value_current = 9999.0f;
00537 float max_value_current = -9999.0f;
00538 if( auto_compute_bounds_property_->getBool() )
00539 {
00540 for( uint32_t i = 0; i < num_points; i++ )
00541 {
00542 float val = values[ i ];
00543 min_value_current = std::min( min_value_current, val );
00544 max_value_current = std::max( max_value_current, val );
00545 }
00546 min_value_property_->setFloat( min_value_current );
00547 max_value_property_->setFloat( max_value_current );
00548 }
00549 else
00550 {
00551 min_value_current = min_value_property_->getFloat();
00552 max_value_current = max_value_property_->getFloat();
00553 }
00554
00555 float range = max_value_current - min_value_current;
00556 if( range == 0 )
00557 {
00558 range = 0.001f;
00559 }
00560 for( uint32_t i = 0; i < num_points; ++i )
00561 {
00562 float value = 1.0 - ( values[ i ] - min_value_current ) / range;
00563 getRainbowColor( value, points_out[i].color );
00564 }
00565
00566 return true;
00567 }
00568
00569 void AxisColorPCTransformer::createProperties( Property* parent_property, uint32_t mask, QList<Property*>& out_props )
00570 {
00571 if( mask & Support_Color )
00572 {
00573 axis_property_ = new EnumProperty( "Axis", "Z",
00574 "The axis to interpolate the color along.",
00575 parent_property, SIGNAL( needRetransform() ), this );
00576 axis_property_->addOption( "X", AXIS_X );
00577 axis_property_->addOption( "Y", AXIS_Y );
00578 axis_property_->addOption( "Z", AXIS_Z );
00579
00580 auto_compute_bounds_property_ = new BoolProperty( "Autocompute Value Bounds", true,
00581 "Whether to automatically compute the value min/max values.",
00582 parent_property, SLOT( updateAutoComputeBounds() ), this );
00583
00584 min_value_property_ = new FloatProperty( "Min Value", -10,
00585 "Minimum value value, used to interpolate the color of a point.",
00586 auto_compute_bounds_property_ );
00587
00588 max_value_property_ = new FloatProperty( "Max Value", 10,
00589 "Maximum value value, used to interpolate the color of a point.",
00590 auto_compute_bounds_property_ );
00591
00592 use_fixed_frame_property_ = new BoolProperty( "Use Fixed Frame", true,
00593 "Whether to color the cloud based on its fixed frame position or its local frame position.",
00594 parent_property, SIGNAL( needRetransform() ), this );
00595
00596 out_props.push_back( axis_property_ );
00597 out_props.push_back( auto_compute_bounds_property_ );
00598 out_props.push_back( use_fixed_frame_property_ );
00599
00600 updateAutoComputeBounds();
00601 }
00602 }
00603
00604 void AxisColorPCTransformer::updateAutoComputeBounds()
00605 {
00606 bool auto_compute = auto_compute_bounds_property_->getBool();
00607 min_value_property_->setHidden( auto_compute );
00608 max_value_property_->setHidden( auto_compute );
00609 if( auto_compute )
00610 {
00611 disconnect( min_value_property_, SIGNAL( changed() ), this, SIGNAL( needRetransform() ));
00612 disconnect( max_value_property_, SIGNAL( changed() ), this, SIGNAL( needRetransform() ));
00613 }
00614 else
00615 {
00616 connect( min_value_property_, SIGNAL( changed() ), this, SIGNAL( needRetransform() ));
00617 connect( max_value_property_, SIGNAL( changed() ), this, SIGNAL( needRetransform() ));
00618 auto_compute_bounds_property_->expand();
00619 }
00620 Q_EMIT needRetransform();
00621 }
00622
00623 }
00624
00625 #include <pluginlib/class_list_macros.h>
00626 PLUGINLIB_EXPORT_CLASS( rviz::AxisColorPCTransformer, rviz::PointCloudTransformer )
00627 PLUGINLIB_EXPORT_CLASS( rviz::FlatColorPCTransformer, rviz::PointCloudTransformer )
00628 PLUGINLIB_EXPORT_CLASS( rviz::IntensityPCTransformer, rviz::PointCloudTransformer )
00629 PLUGINLIB_EXPORT_CLASS( rviz::RGB8PCTransformer, rviz::PointCloudTransformer )
00630 PLUGINLIB_EXPORT_CLASS( rviz::RGBF32PCTransformer, rviz::PointCloudTransformer )
00631 PLUGINLIB_EXPORT_CLASS( rviz::XYZPCTransformer, rviz::PointCloudTransformer )