14#include <boost/geometry.hpp>
15#include <boost/geometry/algorithms/append.hpp>
16#include <boost/geometry/geometries/multi_point.hpp>
19#include <Eigen/Geometry>
20#include <Eigen/src/Geometry/Transform.h>
22#include <range/v3/algorithm/all_of.hpp>
23#include <range/v3/algorithm/min.hpp>
24#include <range/v3/range/conversion.hpp>
25#include <range/v3/view/enumerate.hpp>
26#include <range/v3/view/filter.hpp>
27#include <range/v3/view/reverse.hpp>
28#include <range/v3/view/transform.hpp>
29#include <range/v3/view/zip_with.hpp>
31#include <SimoxUtility/color/Color.h>
32#include <SimoxUtility/color/cmaps/colormaps.h>
33#include <SimoxUtility/shapes/OrientedBox.h>
34#include <VirtualRobot/Nodes/RobotNode.h>
35#include <VirtualRobot/Robot.h>
55#include <armarx/navigation/safety_guard/aron/LaserBasedProximityParams.aron.generated.h>
60namespace rv = ::ranges::views;
68 inline core::TwistLimits
69 mergeSafetyLimits(
const std::vector<std::optional<core::TwistLimits>>& resultsAll)
71 const auto isValidFn =
72 [](
const std::optional<core::TwistLimits>& result)
noexcept ->
bool
73 {
return result.has_value(); };
75 const std::vector<core::TwistLimits> validResults =
76 resultsAll | rv::filter(isValidFn) |
78 [](
const std::optional<core::TwistLimits>& result)
noexcept -> core::TwistLimits
79 {
return result.value(); }) |
83 if (validResults.empty())
88 const std::vector<float> linearLimits =
90 rv::transform([](
const core::TwistLimits& result)
noexcept ->
float
91 {
return result.linear; }) |
94 const std::vector<float> angularLimits =
96 rv::transform([](
const core::TwistLimits& result)
noexcept ->
float
97 {
return result.angular; }) |
100 const core::TwistLimits combinedResult{.linear = ranges::min(linearLimits),
101 .angular = ranges::min(angularLimits)};
103 return combinedResult;
118 arondto::LaserBasedProximityParams dto;
128 arondto::LaserBasedProximityParams dto;
143 generalConfig(generalConfig),
144 humanColorMap_(
simox::color::cmaps::BuPu().reversed()),
145 laserColorMap_(
simox::color::cmaps::OrRd().reversed())
154 auto layer =
viz.layer(
"safety_guard");
156 const std::optional<core::TwistLimits> resultHumans = safetyLimitsHumans(layer);
157 const std::optional<core::TwistLimits> resultLaserScanners =
158 safetyLimitsLaserScanners(layer, global_V_movement);
164 mergeSafetyLimits({resultHumans, resultLaserScanners});
171 std::optional<core::TwistLimits>
172 LaserBasedProximity::safetyLimitsLaserScanners(
viz::Layer& layer,
173 const Eigen::Vector2f& global_V_movement)
const
190 debugObserver.setDebugObserverDatafield(
"numLaserScannerFeatures",
192 debugObserver.setDebugObserverDatafield(
193 "numFirstLaserScannerFeatures",
200 const core::Pose robot_T_global{global_T_robot.inverse()};
205 boost::geometry::model::multi_point<util::geometry::point_type> robotNodes;
206 for (
const auto& node :
scene.robot->getRobotNodes())
209 boost::geometry::append(robotNodes,
212 boost::geometry::convex_hull(robotNodes, convexHull);
216 const auto minDistanceFn =
218 const memory::LaserScannerFeatures& features) -> DistanceAndClosestPoint
220 const core::Pose& global_T_sensor = features.frameGlobalPose;
223 const auto minDistanceFnInner =
224 [
this, &
convexHull, &global_T_sensor, &robot_T_global](
225 const memory::LaserScannerFeature& feature) -> DistanceAndClosestPoint
228 const std::vector<Eigen::Vector3f> points3dGlobal =
230 rv::transform([&global_T_sensor](
const Eigen::Vector2f& pt) -> Eigen::Vector3f
231 {
return global_T_sensor *
conv::to3D(pt); }) |
234 return calculateMinDistance(convexHull, robot_T_global, points3dGlobal);
238 const std::vector<DistanceAndClosestPoint> distances =
239 features.features | rv::transform(minDistanceFnInner) | ranges::to_vector;
241 if (distances.empty())
243 return DistanceAndClosestPoint{.distance = std::numeric_limits<float>::max(),
244 .closestPoint = Eigen::Vector2f::Zero()};
247 return ranges::min(distances, std::less{}, &DistanceAndClosestPoint::distance);
250 const std::vector<DistanceAndClosestPoint> minDistanceToObstacles =
251 scene.dynamicScene->laserScannerFeatures | rv::transform(minDistanceFn) |
254 const auto result = [&]() -> std::optional<InternalVelocityLimitResult>
256 switch (
params.laserScannerProximityField.mode)
261 return velocityLimitsDirectionDependent(
262 global_V_movement, minDistanceToObstacles, global_T_robot);
264 return velocityLimitsDirectionIndependent(minDistanceToObstacles);
268 <<
static_cast<int>(
params.laserScannerProximityField.mode);
277 simox::color::Color color = simox::Color::blue();
278 viz::Polygon vizPoly(
"robot_convex_hull");
279 for (
const auto& p : rv::reverse(
convexHull.outer()))
281 Eigen::Vector2f pt(p.x(), p.y());
284 layer.
add(vizPoly.color(color).lineColor(color));
287 layer.
add(viz::Line(
"robot_movement_Dir")
288 .fromTo(global_T_robot.translation(),
289 global_T_robot.translation() +
290 conv::to3D(global_V_movement).normalized() * 1000)
292 .color(simox::Color::blue()));
295 for (
const auto& [i, region] : ranges::views::enumerate(
params.ignoredRegions))
297 viz::Polygon vizRegion(
"ignored_region_" + std::to_string(i));
298 vizRegion.addPoint(Eigen::Vector3f(region.min().x(), region.min().y(), 15));
299 vizRegion.addPoint(Eigen::Vector3f(region.max().x(), region.min().y(), 15));
300 vizRegion.addPoint(Eigen::Vector3f(region.max().x(), region.max().y(), 15));
301 vizRegion.addPoint(Eigen::Vector3f(region.min().x(), region.max().y(), 15));
302 layer.
add(vizRegion.color(simox::Color::magenta()));
306 for (
const auto& [i, obj] :
307 ranges::views::enumerate(
scene.dynamicScene->attachedObjects))
309 const auto& oobb = obj->oobbGlobal();
312 simox::OrientedBoxf inflatedOobb{
313 oobb->transformation_centered(),
314 oobb->dimensions() + Eigen::Vector3f::Ones() *
params.attachedObjectsInflation};
316 viz::Box vizObj(
"attached_object_" + std::to_string(i));
317 vizObj.set(inflatedOobb);
318 layer.
add(vizObj.color(simox::Color::magenta()));
322 if (not result.has_value())
330 const auto color = laserColorMap_.at(
331 std::abs(result->twistLimits.linear), 0, generalConfig.maxVel.linear);
333 layer.
add(viz::Line(
"nearest_obstacle")
334 .fromTo(global_T_robot.translation(),
conv::to3D(result->closestPoint))
339 return result->twistLimits;
342 std::optional<core::TwistLimits>
343 LaserBasedProximity::safetyLimitsHumans(viz::Layer& layer)
const
345 if (not
params.enableHumans)
354 const auto minimalSegmentDistanceToRobot =
355 [&global_T_robot](
const human::Human& human) ->
float
357 const Eigen::Isometry3f global_T_human =
conv::to3D(human.pose);
358 const Eigen::Isometry3f robot_T_human = global_T_robot.inverse() * global_T_human;
361 return robot_T_human.translation().norm();
364 if (
scene.dynamicScene->humans.empty())
370 const auto distanceRobotToHumans =
scene.dynamicScene->humans |
371 rv::transform(minimalSegmentDistanceToRobot) |
374 const std::optional<float> minDistanceToHumans =
scene.dynamicScene->humans.empty()
375 ? std::optional<float>{std::nullopt}
376 : ranges::min(distanceRobotToHumans);
379 if (not minDistanceToHumans.has_value())
385 evaluateProximityField(
params.humanProximityField, minDistanceToHumans.value());
389 const auto color = humanColorMap_(result.second).with_alpha(1 - result.second + 0.1);
391 layer.
add(viz::Cylinder(
"distance_humans")
392 .position(global_T_robot.translation() + Eigen::Vector3f{0, 0, 10})
393 .direction(Eigen::Vector3f::UnitZ())
394 .radius(minDistanceToHumans.value())
402 LaserBasedProximity::DistanceAndClosestPoint
403 LaserBasedProximity::calculateMinDistance(
406 const std::vector<Eigen::Vector3f>& globalPoints)
const
411 const std::vector<Eigen::Vector2f> points2dGlobal =
413 rv::transform([](
const Eigen::Vector3f& pt) -> Eigen::Vector2f
417 if ( globalPoints.empty())
420 return {.distance = std::numeric_limits<float>::max(),
421 .closestPoint = Eigen::Vector2f::Zero()};
425 const std::vector<float> pointsDistanceRobotCenter =
427 rv::transform([&robot_T_global](
const Eigen::Vector3f& pt) ->
float
428 {
return conv::to2D(robot_T_global * pt).norm(); }) |
431 if (
params.enableLaserScannerFiltering &&
432 globalPoints.size() <=
static_cast<std::size_t
>(
params.laserScannerFilteringThreshold))
436 <<
" points. Distances to robot center: " << pointsDistanceRobotCenter;
438 if (ranges::all_of(pointsDistanceRobotCenter,
439 [&](
float distanceToCenter)
noexcept
441 return std::max(distanceToCenter -
params.robotRadius, 0.F) <
442 params.laserScannerMaxFilteringDistance;
447 return {.distance = std::numeric_limits<float>::max(),
448 .closestPoint = Eigen::Vector2f::Zero()};
452 const auto distancesAndClosestPoint =
454 [
this, &convexHull](
const Eigen::Vector2f& pt,
455 float distanceToCenter) -> DistanceAndClosestPoint
457 const auto distanceToConvexHull =
static_cast<float>(boost::geometry::distance(
460 const float distanceUsingRobotRadius =
461 std::max(distanceToCenter -
params.robotRadius, 0.F);
463 return {.distance = std::min(distanceToConvexHull, distanceUsingRobotRadius),
467 pointsDistanceRobotCenter) |
471 distancesAndClosestPoint, std::less{}, &DistanceAndClosestPoint::distance);
474 std::pair<core::TwistLimits, float>
476 float minDistance)
const
478 if (minDistance < proximityField.safetyDistance)
483 if (minDistance > proximityField.influenceDistance)
489 const float proximityRange =
490 proximityField.influenceDistance - proximityField.safetyDistance;
491 const float clippedDistance =
492 std::min(minDistance, proximityField.influenceDistance) - proximityField.safetyDistance;
494 const float fractionalDistance = std::clamp(clippedDistance / proximityRange, 0.F, 1.F);
496 if (not proximityField.reduceVelocity)
501 const float d_s = std::pow(1 - fractionalDistance, proximityField.k);
503 const auto permissibleVelocity = [&proximityField](
const float d_s,
504 const float v_max) ->
float
505 {
return v_max / (1 + proximityField.lambda * d_s); };
507 const core::TwistLimits result{
508 .linear = permissibleVelocity(d_s, generalConfig.maxVel.linear),
509 .angular = permissibleVelocity(d_s, generalConfig.maxVel.angular)};
511 return {result, fractionalDistance};
514 template <
class T,
class Func>
516 allPointsInside(
const std::vector<T>& points, Func isInsideFunc)
518 for (
const auto& p : points)
520 if (not isInsideFunc(p))
530 LaserBasedProximity::isFeatureIgnored(
531 const std::vector<Eigen::Vector3f>& featurePoints3DGlobal,
532 const std::vector<Eigen::Vector2f>& featurePoints2DGlobal)
const
537 for (
const auto& region :
params.ignoredRegions)
541 if (allPointsInside(featurePoints2DGlobal,
542 [®ion](
const Eigen::Vector2f& p) {
return region.contains(p); }))
550 for (
const auto* o :
scene.dynamicScene->attachedObjects)
552 const auto& oobbGlobal = o->oobbGlobal();
555 simox::OrientedBoxf inflatedOobb{oobbGlobal->transformation_centered(),
556 oobbGlobal->dimensions() +
557 Eigen::Vector3f::Ones() *
558 params.attachedObjectsInflation};
560 if (allPointsInside(featurePoints3DGlobal,
561 [&inflatedOobb](
const Eigen::Vector3f& p)
562 {
return inflatedOobb.contains(p); }))
572 LaserBasedProximity::InternalVelocityLimitResult
573 LaserBasedProximity::velocityLimitsDirectionDependent(
574 const Eigen::Vector2f& global_V_movement,
575 const std::vector<DistanceAndClosestPoint>& minDistanceToObstacles,
576 const Eigen::Isometry3f& global_T_robot)
const
579 float maxPermissibleRelativeVelocityLinear = generalConfig.maxVel.linear;
581 Eigen::Vector2f minPoint = Eigen::Vector2f::Zero();
582 float minDistance = std::numeric_limits<float>::max();
584 const Eigen::Isometry3f robot_T_global = global_T_robot.inverse();
587 auto& debugObserver = *
context.debugObserver;
589 debugObserver.setDebugObserverDatafield(
"numObstacles", minDistanceToObstacles.size());
593 for (
const auto& [
distance, global_P_obstacle_pt] : minDistanceToObstacles)
595 const float ds =
params.laserScannerProximityField.safetyDistance;
596 const float di =
params.laserScannerProximityField.influenceDistance;
599 float velocity_damper = generalConfig.maxVel.linear * (
distance - ds) / (di - ds);
603 const Eigen::Vector2f minPointGlobal = global_P_obstacle_pt;
608 const Eigen::Vector2f directionRobotToObstacle =
609 (minPointGlobal - global_T_robot.translation().head<2>()).normalized();
616 const float relativeVelocityScaling =
617 directionRobotToObstacle.dot(global_V_movement.normalized());
623 if (relativeVelocityScaling < 0)
630 maxPermissibleRelativeVelocityLinear = 0;
631 minPoint = minPointGlobal;
637 if (std::abs(relativeVelocityScaling) > 1e-4)
639 const float thisMaxPermissibileRelVelLinear =
640 velocity_damper / relativeVelocityScaling;
643 if (thisMaxPermissibileRelVelLinear < maxPermissibleRelativeVelocityLinear)
645 maxPermissibleRelativeVelocityLinear = thisMaxPermissibileRelVelLinear;
646 minPoint = minPointGlobal;
651 debugObserver.setDebugObserverDatafield(
"maxPermissibleRelativeVelocityLinear",
652 maxPermissibleRelativeVelocityLinear);
653 debugObserver.setDebugObserverDatafield(
"minDistance", minDistance);
655 core::TwistLimits result{.linear = std::max<float>(0, maxPermissibleRelativeVelocityLinear),
656 .angular = generalConfig.maxVel.angular};
657 return {.twistLimits = result, .minDistance = minDistance, .closestPoint = minPoint};
660 LaserBasedProximity::InternalVelocityLimitResult
661 LaserBasedProximity::velocityLimitsDirectionIndependent(
662 const std::vector<DistanceAndClosestPoint>& minDistanceToObstacles)
const
664 const DistanceAndClosestPoint minDistance =
665 ranges::min(minDistanceToObstacles, std::less{}, &DistanceAndClosestPoint::distance);
668 evaluateProximityField(
params.laserScannerProximityField, minDistance.distance);
670 return {.twistLimits = result.first,
671 .minDistance = minDistance.distance,
672 .closestPoint = minDistance.closestPoint};
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
SafetyGuardResult computeSafetyLimits(const Eigen::Vector2f &global_V_movement) override
LaserBasedProximityParams Params
LaserBasedProximity(const Params ¶ms, const core::GeneralConfig &generalConfig, const core::Scene &scene, const Context &ctx)
SafetyGuard(const core::Scene &scene, const Context &ctx)
const core::Scene & scene
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO
The normal logging level.
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
#define ARMARX_VERBOSE
The logging level for verbose information.
ConvexHull< Point >::type convexHull(const std::vector< Point > &points)
std::shared_ptr< Dict > DictPtr
std::vector< Eigen::Vector2f > to2D(const std::vector< Eigen::Vector3f > &v)
std::vector< Eigen::Vector3f > to3D(const std::vector< Eigen::Vector2f > &v)
This file is part of ArmarX.
void fromAron(const arondto::ProximityFieldParams &dto, ProximityFieldParams &bo)
void toAron(arondto::ProximityFieldParams &dto, const ProximityFieldParams &bo)
boost::geometry::model::d2::point_xy< float > point_type
boost::geometry::model::polygon< point_type > polygon_type
This file is part of ArmarX.
double distance(const Point &a, const Point &b)
VirtualRobot::RobotPtr robot
std::optional< core::DynamicScene > dynamicScene
static TwistLimits ZeroLimits()
static TwistLimits NoLimits()
static LaserBasedProximityParams FromAron(const aron::data::DictPtr &dict)
aron::data::DictPtr toAron() const override
Algorithms algorithm() const override
std::experimental::observer_ptr< DebugObserverComponentPluginUser > debugObserver
void add(ElementT const &element)