LaserBasedProximity.cpp
Go to the documentation of this file.
1 #include "LaserBasedProximity.h"
2 
3 #include <optional>
4 #include <vector>
5 
6 #include <boost/geometry.hpp>
7 #include <boost/geometry/algorithms/append.hpp>
8 #include <boost/geometry/geometries/multi_point.hpp>
9 
10 #include <Eigen/Geometry>
11 
12 #include <SimoxUtility/color/cmaps/colormaps.h>
13 #include <VirtualRobot/Nodes/RobotNode.h>
14 #include <VirtualRobot/Robot.h>
15 
17 
23 
27 #include <armarx/navigation/safety_guard/aron/LaserBasedProximityParams.aron.generated.h>
29 
30 #include <range/v3/algorithm/min.hpp>
31 #include <range/v3/range/conversion.hpp>
32 #include <range/v3/view/filter.hpp>
33 #include <range/v3/view/reverse.hpp>
34 #include <range/v3/view/transform.hpp>
35 #include <range/v3/view/zip_with.hpp>
36 
37 namespace rv = ::ranges::views;
38 
40 {
41 
42  namespace
43  {
44 
45  inline core::TwistLimits
46  mergeSafetyLimits(const std::vector<std::optional<core::TwistLimits>>& resultsAll)
47  {
48  const auto isValidFn = [](const std::optional<core::TwistLimits>& result) -> bool
49  { return result.has_value(); };
50 
51  const std::vector<core::TwistLimits> validResults =
52  resultsAll | rv::filter(isValidFn) |
54  [](const std::optional<core::TwistLimits>& result) -> core::TwistLimits
55  { return result.value(); }) |
56  ranges::to_vector;
57 
58 
59  if (validResults.empty())
60  {
62  }
63 
64  const std::vector<float> linearLimits =
65  validResults |
66  rv::transform([](const core::TwistLimits& result) -> float
67  { return result.linear; }) |
68  ranges::to_vector;
69 
70  const std::vector<float> angularLimits =
71  validResults |
72  rv::transform([](const core::TwistLimits& result) -> float
73  { return result.angular; }) |
74  ranges::to_vector;
75 
76  const core::TwistLimits combinedResult{.linear = ranges::min(linearLimits),
77  .angular = ranges::min(angularLimits)};
78 
79  return combinedResult;
80  }
81 
82  struct PairCompare
83  {
84  bool
85  operator()(const auto& a, const auto& b)
86  {
87  return a.first < b.first;
88  }
89  };
90 
91  } // namespace
92 
95  {
97  }
98 
101  {
102  arondto::LaserBasedProximityParams dto;
103 
105 
106  return dto.toAron();
107  }
108 
111  {
112  arondto::LaserBasedProximityParams dto;
113  dto.fromAron(dict);
114 
116  fromAron(dto, bo);
117 
118  return bo;
119  }
120 
122  const core::Scene& scene,
123  const Context& ctx) :
124  SafetyGuard(scene, ctx),
125  params(params),
126  humanColorMap_(simox::color::cmaps::BuPu().reversed()),
127  laserColorMap_(simox::color::cmaps::OrRd().reversed())
128  {
129  }
130 
133  {
134  ARMARX_CHECK(scene.dynamicScene.has_value());
135 
136  auto layer = viz.layer("safety_guard");
137 
138  const std::optional<core::TwistLimits> resultHumans = safetyLimitsHumans(layer);
139  const std::optional<core::TwistLimits> resultLaserScanners =
140  safetyLimitsLaserScanners(layer);
141 
142  // Commit always. This ensures that objects eventually will be cleared.
143  viz.commit(layer);
144 
145  const core::TwistLimits combinedResult =
146  mergeSafetyLimits({resultHumans, resultLaserScanners});
147  ARMARX_VERBOSE << "Safety limits: " << VAROUT(combinedResult.linear)
148  << VAROUT(combinedResult.angular);
149 
150  return SafetyGuardResult{.twistLimits = combinedResult};
151  }
152 
153  std::optional<core::TwistLimits>
154  LaserBasedProximity::safetyLimitsLaserScanners(viz::Layer& layer) const
155  {
156  if (not params.enableLaserScanners)
157  {
158  return std::nullopt;
159  }
160 
161  if (scene.dynamicScene->laserScannerFeatures.empty())
162  {
163  ARMARX_INFO << "No laser scanner features";
164 
165  return std::nullopt;
166  }
167 
168  const core::Pose global_T_robot{scene.robot->getGlobalPose()};
169  const core::Pose robot_T_global{global_T_robot.inverse()};
170 
171  // compute distance based on convex hull of robot
173  {
174  boost::geometry::model::multi_point<util::geometry::point_type> robotNodes;
175  for (const auto& node : scene.robot->getRobotNodes())
176  {
177  Eigen::Vector2f pos2d = conv::to2D(core::Pose(node->getGlobalPose())).translation();
178  boost::geometry::append(robotNodes,
179  util::geometry::point_type(pos2d.x(), pos2d.y()));
180  }
182  }
183 
184  // Compute the minimal distance to the robot for a set of features (point clusters)
185  const auto minDistanceFn =
186  [this, &convexHull, &robot_T_global](
187  const memory::LaserScannerFeatures& features) -> std::pair<float, Eigen::Vector2f>
188  {
189  const core::Pose& global_T_sensor = features.frameGlobalPose;
190 
191  // Compute the minimal distance to the robot for a single feature (point cluster)
192  const auto minDistanceFnInner =
193  [this, &convexHull, &global_T_sensor, &robot_T_global](
194  const memory::LaserScannerFeature& feature) -> std::pair<float, Eigen::Vector2f>
195  {
196  // transform points to 3D global frame
197  const std::vector<Eigen::Vector3f> points3dGlobal =
198  feature.points |
199  rv::transform([&global_T_sensor](const Eigen::Vector2f& pt) -> Eigen::Vector3f
200  { return global_T_sensor * conv::to3D(pt); }) |
201  ranges::to_vector;
202 
203  return calculateMinDistance(convexHull, robot_T_global, points3dGlobal);
204  };
205 
206  // Compute the minimal distance to the robot for all features (point clusters)
207  const std::vector<std::pair<float, Eigen::Vector2f>> distances =
208  features.features | rv::transform(minDistanceFnInner) | ranges::to_vector;
209 
210  return ranges::min(distances, PairCompare());
211  };
212 
213  const std::vector<std::pair<float, Eigen::Vector2f>> minDistanceToObstacles =
214  scene.dynamicScene->laserScannerFeatures | rv::transform(minDistanceFn) |
215  ranges::to_vector;
216 
217  const std::pair<float, Eigen::Vector2f> minDistance =
218  ranges::min(minDistanceToObstacles, PairCompare());
219 
220  const auto result =
221  evaluateProximityField(params.laserScannerProximityField, minDistance.first);
222 
223 
224  {
225  //visualize convex hull and nearest obstacle
226 
227  // increase transparency with increased distance, but never make fully transparent
228  const auto color = laserColorMap_(result.second);
229 
230  viz::Polygon vizPoly("robot_convex_hull");
231  for (const auto& p : rv::reverse(convexHull.outer()))
232  {
233  Eigen::Vector2f pt(p.x(), p.y());
234  vizPoly.addPoint(conv::to3D(pt));
235  }
236  layer.add(vizPoly.color(color).lineColor(color));
237 
238  layer.add(viz::Line("nearest_obstacle")
239  .fromTo(global_T_robot.translation(), conv::to3D(minDistance.second))
240  .lineWidth(100.F)
241  .color(color));
242  }
243 
244  return result.first;
245  }
246 
247  std::optional<core::TwistLimits>
248  LaserBasedProximity::safetyLimitsHumans(viz::Layer& layer) const
249  {
250  if (not params.enableHumans)
251  {
252  return std::nullopt;
253  }
254 
255  const core::Pose global_T_robot{scene.robot->getGlobalPose()};
256 
257  // Compute the minimal distance to the robot for a human
258  // TODO(utetg): is minimum distance = distance to robot center or robot edge
259  const auto minimalSegmentDistanceToRobot =
260  [&global_T_robot](const human::Human& human) -> float
261  {
262  const Eigen::Isometry3f global_T_human = conv::to3D(human.pose);
263  const Eigen::Isometry3f robot_T_human = global_T_robot.inverse() * global_T_human;
264 
265  // TODO extension: if also human keypoints are available, use them to compute the minimal distance
266  return robot_T_human.translation().norm();
267  };
268 
269  if (scene.dynamicScene->humans.empty())
270  {
271  ARMARX_VERBOSE << "No humans";
272  return std::nullopt;
273  }
274 
275  const auto distanceRobotToHumans = scene.dynamicScene->humans |
276  rv::transform(minimalSegmentDistanceToRobot) |
277  ranges::to_vector;
278 
279  const std::optional<float> minDistanceToHumans = scene.dynamicScene->humans.empty()
280  ? std::optional<float>{std::nullopt}
281  : ranges::min(distanceRobotToHumans);
282 
283  // handle if no humans are detected
284  if (not minDistanceToHumans.has_value())
285  {
287  }
288 
289  const auto result =
290  evaluateProximityField(params.humanProximityField, minDistanceToHumans.value());
291 
292  {
293  // increase transparency with increased distance, but never make fully transparent
294  const auto color = humanColorMap_(result.second).with_alpha(1 - result.second + 0.1);
295 
296  layer.add(viz::Cylinder("distance_humans")
297  .position(global_T_robot.translation() + Eigen::Vector3f{0, 0, 10})
298  .direction(Eigen::Vector3f::UnitZ())
299  .radius(minDistanceToHumans.value())
300  .height(10)
301  .color(color));
302  }
303 
304  return result.first;
305  }
306 
307  std::pair<float, Eigen::Vector2f>
308  LaserBasedProximity::calculateMinDistance(
310  const core::Pose& robot_T_global,
311  const std::vector<Eigen::Vector3f>& globalPoints) const
312  {
313  // Compute the minimal distance to the robot for a list of points
314 
315  // transform points to 2D global frame
316  const std::vector<Eigen::Vector2f> points2dGlobal =
317  globalPoints |
318  rv::transform([](const Eigen::Vector3f& pt) -> Eigen::Vector2f
319  { return conv::to2D(pt); }) |
320  ranges::to_vector;
321 
322  // calculate distance to robot center
323  const std::vector<float> pointsDistanceRobotCenter =
324  globalPoints |
325  rv::transform([&robot_T_global](const Eigen::Vector3f& pt) -> float
326  { return conv::to2D(robot_T_global * pt).norm(); }) |
327  ranges::to_vector;
328 
329  return ranges::min(
330  rv::zip_with(
331  [this, &convexHull](const Eigen::Vector2f& pt,
332  float distanceToCenter) -> std::pair<float, Eigen::Vector2f>
333  {
334  const auto distanceToConvexHull = static_cast<float>(boost::geometry::distance(
335  util::geometry::point_type(pt.x(), pt.y()), convexHull));
336 
337  const float distanceUsingRobotRadius =
338  std::max(distanceToCenter - params.robotRadius, 0.F);
339 
340  return {std::min(distanceToConvexHull, distanceUsingRobotRadius), pt};
341  },
342  points2dGlobal,
343  pointsDistanceRobotCenter),
344  PairCompare());
345  }
346 
347  std::pair<core::TwistLimits, float>
348  LaserBasedProximity::evaluateProximityField(const ProximityFieldParams& proximityField,
349  float minDistance) const
350  {
351  if (minDistance < proximityField.minDistance)
352  {
353  return {core::TwistLimits::ZeroLimits(), 0.F};
354  }
355 
356  if (minDistance > proximityField.maxDistance)
357  {
358  return {core::TwistLimits::NoLimits(), 1.F};
359  }
360 
361 
362  const float proximityRange = proximityField.maxDistance - proximityField.minDistance;
363  const float clippedDistance =
364  std::min(minDistance, proximityField.maxDistance) - proximityField.minDistance;
365 
366  const float fractionalDistance = std::clamp(clippedDistance / proximityRange, 0.F, 1.F);
367 
368  if (not proximityField.reduceVelocity)
369  {
370  return {core::TwistLimits::NoLimits(), fractionalDistance};
371  }
372 
373  const float d_s = std::pow(1 - fractionalDistance, proximityField.k);
374 
375  const auto permissibleVelocity = [&proximityField](const float d_s,
376  const float v_max) -> float
377  { return v_max / (1 + proximityField.lambda * d_s); };
378 
379  const core::TwistLimits result{
380  .linear = permissibleVelocity(d_s, params.maxVelocity.linear),
381  .angular = permissibleVelocity(d_s, params.maxVelocity.angular)};
382 
383  return {result, fractionalDistance};
384  }
385 
386 } // namespace armarx::navigation::safety_guard
Client.h
armarx::navigation::safety_guard::LaserBasedProximityParams::enableLaserScanners
bool enableLaserScanners
Definition: LaserBasedProximity.h:62
armarx::navigation::core::Scene::robot
VirtualRobot::RobotPtr robot
Definition: types.h:67
armarx::navigation::core::TwistLimits::ZeroLimits
static TwistLimits ZeroLimits()
Definition: types.h:93
ARMARX_VERBOSE
#define ARMARX_VERBOSE
Definition: Logging.h:187
armarx::navigation::safety_guard::LaserBasedProximityParams
Definition: LaserBasedProximity.h:57
armarx::viz::Cylinder::radius
Cylinder & radius(float r)
Definition: Elements.h:76
armarx::navigation::core::Pose
Eigen::Isometry3f Pose
Definition: basic_types.h:31
armarx::navigation::safety_guard::fromAron
void fromAron(const arondto::ProximityFieldParams &dto, ProximityFieldParams &bo)
Definition: aron_conversions.cpp:32
armarx::navigation::safety_guard
This file is part of ArmarX.
Definition: fwd.h:54
LaserBasedProximity.h
Layer.h
Convex::convexHull
ConvexHull< Point >::type convexHull(const std::vector< Point > &points)
Definition: convexHull.hpp:501
armarx::navigation::core::TwistLimits::linear
float linear
Definition: types.h:82
armarx::viz::Cylinder::height
Cylinder & height(float h)
Definition: Elements.h:84
armarx::viz::Layer::add
void add(ElementT const &element)
Definition: Layer.h:31
armarx::navigation::safety_guard::LaserBasedProximityParams::humanProximityField
ProximityFieldParams humanProximityField
Definition: LaserBasedProximity.h:69
armarx::navigation::safety_guard::LaserBasedProximityParams::maxVelocity
core::TwistLimits maxVelocity
Definition: LaserBasedProximity.h:64
armarx::navigation::core::TwistLimits::angular
float angular
Definition: types.h:83
ARMARX_CHECK
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
Definition: ExpressionException.h:82
Elements.h
clamp
double clamp(double x, double a, double b)
Definition: point.hpp:136
armarx::navigation::safety_guard::SafetyGuard::Context
Definition: SafetyGuard.h:76
armarx::navigation::util::geometry::point_type
boost::geometry::model::d2::point_xy< float > point_type
Definition: geometry.h:35
armarx::navigation::safety_guard::LaserBasedProximityParams::laserScannerProximityField
ProximityFieldParams laserScannerProximityField
Definition: LaserBasedProximity.h:70
armarx::navigation::safety_guard::LaserBasedProximityParams::FromAron
static LaserBasedProximityParams FromAron(const aron::data::DictPtr &dict)
Definition: LaserBasedProximity.cpp:110
armarx::reverse
T reverse(const T &o)
Definition: container.h:33
armarx::navigation::safety_guard::Algorithms
Algorithms
Definition: core.h:30
armarx::viz::Cylinder::direction
Cylinder & direction(Eigen::Vector3f direction)
Definition: Elements.cpp:103
armarx::ctrlutil::a
double a(double t, double a0, double j)
Definition: CtrlUtil.h:45
Line.h
armarx::navigation::safety_guard::SafetyGuardResult
Definition: SafetyGuard.h:47
Color.h
armarx::viz::Cylinder
Definition: Elements.h:71
armarx::navigation::safety_guard::Algorithms::LaserBasedProximity
@ LaserBasedProximity
armarx::navigation::core::TwistLimits
Definition: types.h:80
max
T max(T t1, T t2)
Definition: gdiam.h:51
armarx::viz::Polygon
Definition: Elements.h:260
armarx::navigation::safety_guard::LaserBasedProximity::params
const Params params
Definition: LaserBasedProximity.h:100
armarx::navigation::safety_guard::SafetyGuardResult::twistLimits
core::TwistLimits twistLimits
Definition: SafetyGuard.h:49
armarx::navigation::safety_guard::LaserBasedProximity::LaserBasedProximity
LaserBasedProximity(const Params &params, const core::Scene &scene, const Context &ctx)
Definition: LaserBasedProximity.cpp:121
armarx::viz::Line::lineWidth
Line & lineWidth(float w)
Definition: Line.cpp:8
armarx::navigation::core::Scene
Definition: types.h:60
aron_conversions.h
armarx::navigation::core::Scene::dynamicScene
std::optional< core::DynamicScene > dynamicScene
Definition: types.h:65
armarx::transform
auto transform(const Container< InputT, Alloc > &in, OutputT(*func)(InputT const &)) -> Container< OutputT, typename std::allocator_traits< Alloc >::template rebind_alloc< OutputT >>
Convenience function (with less typing) to transform a container of type InputT into the same contain...
Definition: algorithm.h:351
armarx::navigation::conv::to2D
std::vector< Eigen::Vector2f > to2D(const std::vector< Eigen::Vector3f > &v)
Definition: eigen.cpp:29
armarx::navigation::util::geometry::polygon_type
boost::geometry::model::polygon< point_type > polygon_type
Definition: geometry.h:36
convex_hull
void convex_hull(vec_point_2d &in, vec_point_2d &out)
Definition: gdiam.cpp:1812
armarx::aron::data::DictPtr
std::shared_ptr< Dict > DictPtr
Definition: Dict.h:41
ARMARX_INFO
#define ARMARX_INFO
Definition: Logging.h:181
VAROUT
#define VAROUT(x)
Definition: StringHelpers.h:198
armarx::viz::ElementOps::color
DerivedT & color(Color color)
Definition: ElementOps.h:218
armarx::navigation::safety_guard::LaserBasedProximityParams::robotRadius
float robotRadius
Definition: LaserBasedProximity.h:59
F
Definition: ExportDialogControllerTest.cpp:18
armarx::navigation::conv::to3D
std::vector< Eigen::Vector3f > to3D(const std::vector< Eigen::Vector2f > &v)
Definition: eigen.cpp:14
armarx::navigation::safety_guard::LaserBasedProximityParams::algorithm
Algorithms algorithm() const override
Definition: LaserBasedProximity.cpp:94
distance
double distance(const Point &a, const Point &b)
Definition: point.hpp:95
armarx::navigation::safety_guard::LaserBasedProximityParams::enableHumans
bool enableHumans
Definition: LaserBasedProximity.h:61
armarx::navigation::safety_guard::LaserBasedProximity::computeSafetyLimits
SafetyGuardResult computeSafetyLimits() override
Definition: LaserBasedProximity.cpp:132
eigen.h
Logging.h
armarx::navigation::safety_guard::SafetyGuard
Definition: SafetyGuard.h:73
min
T min(T t1, T t2)
Definition: gdiam.h:44
armarx::viz::Line
Definition: Line.h:29
armarx::navigation::safety_guard::LaserBasedProximityParams::toAron
aron::data::DictPtr toAron() const override
Definition: LaserBasedProximity.cpp:100
armarx::viz::Layer
Definition: Layer.h:12
simox
Definition: Impl.cpp:40
types.h
armarx::viz
This file is part of ArmarX.
Definition: ArVizStorage.cpp:418
armarx::aron::bo
const std::optional< BoT > & bo
Definition: aron_conversions.h:174
armarx::navigation::core::TwistLimits::NoLimits
static TwistLimits NoLimits()
Definition: types.h:86
SafetyGuard.h
armarx::navigation::safety_guard::toAron
void toAron(arondto::ProximityFieldParams &dto, const ProximityFieldParams &bo)
Definition: aron_conversions.cpp:43
armarx::navigation::safety_guard::SafetyGuard::scene
const core::Scene & scene
Definition: SafetyGuard.h:87