31 #include <Eigen/Geometry>
33 #include <IceUtil/Time.h>
35 #include <SimoxUtility/color/Color.h>
36 #include <VirtualRobot/BoundingBox.h>
37 #include <VirtualRobot/MathTools.h>
51 #include "conversions/eigen.h"
57 const std::string LaserScannerFeatureExtraction::defaultName =
"LaserScannerFeatureExtraction";
66 def->topic(featuresTopic);
70 properties.taskPeriodMs,
"p.taskPeriodMs",
"Update rate of the running task.");
72 def->optional(properties.robotHull.shape,
"p.robotHull.shape",
"Shape of the robot area.")
75 def->optional(properties.robotHull.radius,
77 "The radius of the robot when using the circle shape.");
79 properties.robotHull.robotConvexHullMargin,
80 "p.robotHull.robotConvexHullMargin",
81 "Parameter to increase the robot's convex hull when using the rectangle shape.");
83 def->optional(properties.cableFix.enabled,
85 "Try to supress clusters belonging to the power supply cable.");
86 def->optional(properties.cableFix.cableAreaWidth,
87 "p.cableFix.cableAreaWidth",
88 "Width of the area where to search for the cable.");
91 properties.cableFix.maxAreaTh,
92 "p.cableFix.maxAreaTh",
93 "The cable will only be removed if the cluster area is below this threshold.");
96 properties.chainApproximationParams.distanceTh,
"p.chainApproximation.distanceTh",
"");
97 def->optional(properties.chainApproximationParams.maxIterations,
98 "p.chainApproximation.maxIterations",
101 def->optional(properties.scanClusteringParams.angleThreshold,
102 "p.scanClustering.angleThreshold",
103 "Angular distance between consecutive points in laser scan for clustering.");
104 def->optional(properties.scanClusteringParams.distanceThreshold,
105 "p.scanClustering.distanceThreshold",
106 "Radial distance between consecutive points in laser scan for clustering.");
107 def->optional(properties.scanClusteringParams.maxDistance,
108 "p.scanClustering.maxDistance",
109 "Maximum radius around sensors to detect clusters.");
111 def->optional(properties.arviz.drawRawPoints,
112 "p.arviz.drawRawPoints",
113 "If true, the laser scans will be drawn.");
115 def->optional(properties.arviz.visualizeSeparateFeatures,
116 "p.arviz.visualizeSeparateFeatures",
117 "If true, the features from each sensor will be drawn.");
119 def->optional(properties.arviz.visualizeMergedFeatures,
120 "p.arviz.visualizeMergedFeatures",
121 "If true, the merged features from all sensors will be drawn.");
129 addPlugin(laserScannerFeatureWriterPlugin);
147 frequencyReporterPublish = std::make_unique<FrequencyReporter>(
149 frequencyReporterRun = std::make_unique<FrequencyReporter>(
154 featureExtractor = std::make_unique<FeatureExtractor>(properties.scanClusteringParams,
155 properties.chainApproximationParams);
161 switch (properties.robotHull.shape)
167 robot->getRobotNode(
"PlatformCornerFrontLeft")->getPositionInRootFrame());
169 robot->getRobotNode(
"PlatformCornerFrontRight")->getPositionInRootFrame());
171 robot->getRobotNode(
"PlatformCornerBackRight")->getPositionInRootFrame());
173 robot->getRobotNode(
"PlatformCornerBackLeft")->getPositionInRootFrame());
175 const auto addMargin = [&](
const Eigen::Vector2f& pt) -> Eigen::Vector2f
177 Eigen::Vector2f ptWithMargin = pt;
179 std::copysignf(properties.robotHull.robotConvexHullMargin, pt.x());
181 std::copysignf(properties.robotHull.robotConvexHullMargin, pt.y());
187 std::vector<Eigen::Vector2f> hullPoints;
188 hullPoints.push_back(addMargin(ptFrontLeft));
189 hullPoints.push_back(addMargin(ptFrontRight));
190 hullPoints.push_back(addMargin(ptBackRight));
191 hullPoints.push_back(addMargin(ptBackLeft));
193 robotHull = VirtualRobot::MathTools::createConvexHull2D(hullPoints);
197 if (properties.cableFix.enabled)
202 std::vector<Eigen::Vector2f> cableAreaPoints;
203 cableAreaPoints.emplace_back(addMargin(ptBackRight) +
204 100 * Eigen::Vector2f::UnitY());
205 cableAreaPoints.emplace_back(addMargin(ptBackLeft) +
206 100 * Eigen::Vector2f::UnitY());
207 cableAreaPoints.emplace_back(addMargin(ptBackRight) -
208 properties.cableFix.cableAreaWidth *
209 Eigen::Vector2f::UnitY());
210 cableAreaPoints.emplace_back(addMargin(ptBackLeft) -
211 properties.cableFix.cableAreaWidth *
212 Eigen::Vector2f::UnitY());
214 cableArea = VirtualRobot::MathTools::createConvexHull2D(cableAreaPoints);
222 if (properties.cableFix.enabled)
224 ARMARX_ERROR <<
"Cable fix is not supported for circular robots!";
226 const Eigen::Vector2f root =
236 &LaserScannerFeatureExtraction::runPeriodically,
237 properties.taskPeriodMs,
275 const Eigen::Isometry3f& global_T_sensor,
276 const std::string& sensorFrame)
279 armemFeatures.
frame = sensorFrame;
285 return armemFeatures;
288 std::vector<Features>
291 const auto isInvalid = [](
const Features& features) ->
bool
293 if (features.points.size() < 2)
298 if (not features.convexHull.has_value())
317 features.erase(std::remove_if(features.begin(), features.end(), isInvalid), features.end());
322 std::vector<Features>
324 const std::vector<Features>& featuresFromExtractor)
331 const Eigen::Isometry3f global_T_sensor(
332 robot->getRobotNode(
data.header.frame)->getGlobalPose());
333 const Eigen::Isometry3f robot_T_sensor(
334 robot->getRobotNode(
data.header.frame)->getPoseInRootFrame());
339 const auto transformPoints =
340 [](
const std::vector<Eigen::Vector2f>& points,
const Eigen::Isometry3f& tf)
342 std::vector<Eigen::Vector2f> out;
343 out.reserve(points.size());
347 std::back_inserter(out),
348 [&tf](
const auto& pt)
349 { return conversions::to2D(tf * conversions::to3D(pt)); });
354 const auto isPointInsideRobot = [&](
const Eigen::Vector2f& pt) ->
bool
356 if (std::holds_alternative<VirtualRobot::MathTools::ConvexHull2DPtr>(robotHull))
358 return VirtualRobot::MathTools::isInside(
359 pt, std::get<VirtualRobot::MathTools::ConvexHull2DPtr>(robotHull));
361 else if (std::holds_alternative<Eigen::Vector2f>(robotHull))
363 return (std::get<Eigen::Vector2f>(robotHull) - pt).norm() <
364 properties.robotHull.radius;
368 const auto isPointInsideCableArea = [&](
const Eigen::Vector2f& pt) ->
bool
369 {
return VirtualRobot::MathTools::isInside(pt, cableArea); };
370 const auto isClusterInvalid = [&](
const Features& features) ->
bool
373 const auto points = [&]()
375 if (features.convexHull)
377 return transformPoints(features.convexHull->vertices, robot_T_sensor);
380 return transformPoints(features.points, robot_T_sensor);
383 const bool allPointsInsideRobot =
384 std::all_of(points.begin(), points.end(), isPointInsideRobot);
385 if (allPointsInsideRobot)
390 if (properties.cableFix.enabled)
393 const bool allPointsInCableArea =
394 std::all_of(points.begin(), points.end(), isPointInsideCableArea);
395 if (allPointsInCableArea)
397 if (geometry::ConvexHull(points).area() < properties.cableFix.maxAreaTh)
407 const auto removeFeaturesOnRobotOrCable = [&](std::vector<Features> features)
409 features.erase(std::remove_if(features.begin(), features.end(), isClusterInvalid),
414 ARMARX_VERBOSE << featuresFromExtractor.size() <<
" features from extractor";
416 const auto features = removeFeaturesOnRobotOrCable(featuresFromExtractor);
417 ARMARX_VERBOSE << features.size() <<
" features without cable region";
420 ARMARX_VERBOSE << validFeatures.size() <<
" valid features without cable region";
422 const auto armemFeatures =
425 ARMARX_VERBOSE <<
"Reporting " << armemFeatures.features.size() <<
" features";
428 publishFeatures(armemFeatures,
data.header.timestamp);
430 if (properties.arviz.visualizeSeparateFeatures)
433 features, &
data,
data.header.frame,
data.header.timestamp, global_T_sensor);
436 return validFeatures;
440 LaserScannerFeatureExtraction::getOrCreateThrottler(std::string frame)
442 const auto it = throttlers.find(frame);
443 if (it == throttlers.end())
445 throttlers.emplace(frame, Throttler(10.
F));
448 return throttlers.at(frame);
452 LaserScannerFeatureExtraction::drawRobot(
const armem::Time& timestamp)
455 if (getOrCreateThrottler(robot->getName()).
check(timestamp))
457 const Eigen::Isometry3f global_T_robot(robot->getRootNode()->getGlobalPose());
459 if (std::holds_alternative<VirtualRobot::MathTools::ConvexHull2DPtr>(robotHull))
461 VirtualRobot::MathTools::ConvexHull2DPtr& convexHullPtr =
462 std::get<VirtualRobot::MathTools::ConvexHull2DPtr>(robotHull);
463 if (convexHullPtr !=
nullptr)
465 arVizDrawer->draw(
"robot_convex_hull",
468 simox::Color::azure(255, 80));
471 else if (std::holds_alternative<Eigen::Vector2f>(robotHull))
473 Eigen::Vector2f& root = std::get<Eigen::Vector2f>(robotHull);
474 arVizDrawer->draw(
"robot_circle_hull",
475 Circle{root, properties.robotHull.radius},
477 simox::Color::azure(255, 80));
480 if (cableArea !=
nullptr)
483 "cable_area", *cableArea, global_T_robot, simox::Color::blue(255, 80));
489 LaserScannerFeatureExtraction::drawFeatures(
490 const std::vector<Features>& features,
491 const armem::laser_scans::LaserScanStamped* laserPoints,
492 const std::string frame,
494 const Eigen::Isometry3f& global_T_sensor)
497 if (getOrCreateThrottler(frame).check(timestamp))
499 arVizDrawer->draw(features, frame, global_T_sensor);
501 if (properties.arviz.drawRawPoints && laserPoints !=
nullptr)
503 arVizDrawer->draw(*laserPoints, global_T_sensor, simox::Color::magenta(255, 100));
509 LaserScannerFeatureExtraction::publishFeatures(
const memory::LaserScannerFeatures& features,
514 laserScannerFeatureWriterPlugin->get().store(features,
getName(), timestamp);
518 for (
const auto& feature : features.features)
520 if (not feature.chain.empty())
523 for (
const auto& pt : feature.chain)
528 chains.push_back(chain);
532 featuresTopic->reportExtractedLineSegments(chains);
559 LaserScannerFeatureExtraction::runPeriodically()
562 .
agent = robot->getName(), .timeRange = std::nullopt, .sensorList = {}};
565 laserScannerReaderPlugin->get().queryData(laserScanQuery);
573 frequencyReporterRun->add(IceUtil::Time::now().toMicroSeconds());
575 std::vector<FramedFeatures> allFeatures;
576 allFeatures.reserve(laserScanResult.
laserScans.size());
579 for (
const auto& scan : laserScanResult.
laserScans)
581 mostRecentTimestamp =
std::max(mostRecentTimestamp, scan.header.timestamp);
585 const auto features = featureExtractor->onData(scan);
587 auto& framedFeatures = allFeatures.emplace_back();
590 framedFeatures.features = onFeatures(scan, features);
591 framedFeatures.frame = scan.header.frame;
594 if (!allFeatures.empty())
596 mergeFeatures(allFeatures, mostRecentTimestamp);
600 drawRobot(mostRecentTimestamp);
603 frequencyReporterPublish->add(IceUtil::Time::now().toMicroSeconds());
607 LaserScannerFeatureExtraction::mergeFeatures(
const std::vector<FramedFeatures>& framedFeatures,
611 std::vector<Features> converted;
612 for (
const auto& features : framedFeatures)
614 converted.reserve(converted.size() + features.features.size());
615 Eigen::Isometry3f global_T_frame(robot->getRobotNode(features.frame)->getGlobalPose());
617 for (
const auto& singleFeature : features.features)
624 FeatureMerger merger{std::move(converted), properties.chainApproximationParams};
625 std::vector<Features> merged = merger.merge();
628 std::string frame =
"global";
631 const auto armemFeatures =
toArmemFeatures(merged, global_T_global, frame);
633 laserScannerFeatureWriterPlugin->get().store(armemFeatures,
getName(), timestamp);
637 if (properties.arviz.visualizeMergedFeatures)
640 auto global_T_vis = global_T_global;
641 global_T_vis.translation() = Eigen::Vector3f{0, 0, 20};
642 drawFeatures(merged,
nullptr, frame, timestamp, global_T_vis);