37 #include <SimoxUtility/json.h>
38 #include <SimoxUtility/math/convert.h>
39 #include <VirtualRobot/Robot.h>
42 #include <Ice/Current.h>
45 #include <DynamicObstacleAvoidance/Modulation.hpp>
46 #include <DynamicObstacleAvoidance/Obstacle/Ellipsoid.hpp>
47 #include <DynamicObstacleAvoidance/Utils/Plotting/PlottingTools.hpp>
48 namespace doa = DynamicObstacleAvoidance;
60 std::shared_ptr<doa::Obstacle>
61 create_doa_obstacle(
const armarx::obstacledetection::Obstacle& obstacle)
63 Eigen::Vector3d pos(obstacle.posX, obstacle.posY, obstacle.posZ);
66 std::shared_ptr<doa::Ellipsoid> obstacle_ptr =
67 std::make_shared<doa::Ellipsoid>(obstacle.name,
69 Eigen::Array3d(obstacle.safetyMarginX / 1000,
70 obstacle.safetyMarginY / 1000,
71 obstacle.safetyMarginZ / 1000));
73 obstacle_ptr->set_axis_lengths(
74 Eigen::Array3d(obstacle.axisLengthX, obstacle.axisLengthY, obstacle.axisLengthZ) /
76 obstacle_ptr->set_reference_position(
77 Eigen::Vector3d(obstacle.refPosX, obstacle.refPosY, obstacle.refPosZ) / 1000);
78 obstacle_ptr->set_curvature_factor(
79 Eigen::Array3d(obstacle.curvatureX, obstacle.curvatureY, obstacle.curvatureZ));
90 from_json(
const nlohmann::json& j, armarx::obstacledetection::Obstacle& obstacle)
92 j.at(
"name").get_to(obstacle.name);
93 j.at(
"posX").get_to(obstacle.posX);
94 j.at(
"posY").get_to(obstacle.posY);
95 if (j.find(
"posZ") != j.end())
97 j.at(
"posZ").get_to(obstacle.posZ);
99 j.at(
"yaw").get_to(obstacle.yaw);
100 j.at(
"axisLengthX").get_to(obstacle.axisLengthX);
101 j.at(
"axisLengthY").get_to(obstacle.axisLengthY);
102 if (j.find(
"axisLengthZ") != j.end())
104 j.at(
"axisLengthZ").get_to(obstacle.axisLengthZ);
106 j.at(
"refPosX").get_to(obstacle.refPosX);
107 j.at(
"refPosY").get_to(obstacle.refPosY);
108 if (j.find(
"refPosZ") != j.end())
110 j.at(
"refPosZ").get_to(obstacle.refPosZ);
112 if (j.find(
"safetyMarginX") != j.end())
114 j.at(
"safetyMarginX").get_to(obstacle.safetyMarginX);
116 if (j.find(
"safetyMarginY") != j.end())
118 j.at(
"safetyMarginY").get_to(obstacle.safetyMarginY);
120 if (j.find(
"safetyMarginZ") != j.end())
122 j.at(
"safetyMarginZ").get_to(obstacle.safetyMarginZ);
124 if (j.find(
"curvatureX") != j.end())
126 j.at(
"curvatureX").get_to(obstacle.curvatureX);
128 if (j.find(
"curvatureY") != j.end())
130 j.at(
"curvatureY").get_to(obstacle.curvatureY);
132 if (j.find(
"curvatureZ") != j.end())
134 j.at(
"curvatureZ").get_to(obstacle.curvatureZ);
145 m_doa.cfg.only_2d =
true;
146 m_doa.cfg.aggregated =
true;
147 m_doa.cfg.agent_safety_margin = 0;
148 m_doa.cfg.local_modulation =
false;
149 m_doa.cfg.repulsion =
true;
150 m_doa.cfg.planar_modulation =
false;
151 m_doa.cfg.critical_distance = 1.0;
152 m_doa.cfg.weight_power = 2.0;
160 m_buf.needs_env_update =
false;
162 m_doa.env.set_aggregated(m_doa.cfg.aggregated);
164 if (not m_doa.load_obstacles_from_file.empty())
166 const std::filesystem::path scene_obstacles_path =
168 std::ifstream ifs(scene_obstacles_path);
169 nlohmann::json j = nlohmann::json::parse(ifs);
171 std::vector<obstacledetection::Obstacle> obstacles = j;
172 for (obstacledetection::Obstacle& obstacle : obstacles)
174 if (m_doa.cfg.only_2d)
177 obstacle.axisLengthZ = 0;
178 obstacle.refPosZ = 0;
179 obstacle.safetyMarginZ = 0;
180 obstacle.curvatureZ = 1;
183 sanity_check_obstacle(obstacle);
185 m_doa.env.add_obstacle(::create_doa_obstacle(obstacle));
191 writeDebugPlots(
"doa_upstart");
204 this, &DSObstacleAvoidance::run_visualization, m_vis.task_interval);
208 if (m_watchdog.enabled)
211 this, &DSObstacleAvoidance::run_watchdog, m_watchdog.task_interval);
212 m_watchdog.task->start();
228 if (m_watchdog.enabled)
230 m_watchdog.task->stop();
250 armarx::dsobstacleavoidance::Config
260 ARMARX_DEBUG <<
"Adding/updating obstacle: " << obstacle.name <<
".";
262 std::unique_lock l{m_buf.mutex};
264 sanity_check_obstacle(obstacle);
267 update.time = IceUtil::Time::now();
268 update.name = obstacle.name;
271 m_buf.update_log.push_back(
update);
272 m_buf.obstacles[obstacle.name] = obstacle;
273 m_buf.needs_env_update =
true;
275 ARMARX_DEBUG <<
"Added/updated obstacle: " << obstacle.name <<
".";
281 ARMARX_DEBUG <<
"Removing obstacle: " << obstacle_name <<
".";
283 std::unique_lock l{m_buf.mutex};
286 update.time = IceUtil::Time::now();
287 update.name = obstacle_name;
288 update.type = buffer::update_type::removal;
290 m_buf.update_log.push_back(
update);
291 m_buf.needs_env_update =
true;
293 ARMARX_DEBUG <<
"Removed obstacle: " << obstacle_name <<
".";
296 std::vector<armarx::obstacledetection::Obstacle>
300 std::shared_lock l{m_doa.mutex};
302 std::vector<obstacledetection::Obstacle> obstacle_list;
304 for (
auto& [doa_name, doa_obstacle] : m_doa.env)
307 std::shared_ptr<doa::Ellipsoid> doa_ellipsoid =
308 std::dynamic_pointer_cast<doa::Ellipsoid>(doa_obstacle);
309 obstacledetection::Obstacle obstacle;
311 obstacle.name = doa_name;
312 obstacle.posX = doa_ellipsoid->get_position()(0) * 1000;
313 obstacle.posY = doa_ellipsoid->get_position()(1) * 1000;
314 obstacle.posZ = doa_ellipsoid->get_position()(2) * 1000;
315 obstacle.yaw = simox::math::mat3f_to_rpy(
316 doa_ellipsoid->get_orientation().toRotationMatrix().cast<
float>())
318 obstacle.axisLengthX = doa_ellipsoid->get_axis_lengths(0) * 1000;
319 obstacle.axisLengthY = doa_ellipsoid->get_axis_lengths(1) * 1000;
320 obstacle.axisLengthZ = doa_ellipsoid->get_axis_lengths(2) * 1000;
321 obstacle.refPosX = doa_ellipsoid->get_reference_position()(0) * 1000;
322 obstacle.refPosY = doa_ellipsoid->get_reference_position()(1) * 1000;
323 obstacle.refPosZ = doa_ellipsoid->get_reference_position()(2) * 1000;
324 obstacle.safetyMarginX = doa_ellipsoid->get_safety_margin()(0) * 1000;
325 obstacle.safetyMarginY = doa_ellipsoid->get_safety_margin()(1) * 1000;
326 obstacle.safetyMarginZ = doa_ellipsoid->get_safety_margin()(2) * 1000;
327 obstacle.curvatureX = doa_ellipsoid->get_curvature_factor()(0);
328 obstacle.curvatureY = doa_ellipsoid->get_curvature_factor()(1);
329 obstacle.curvatureZ = doa_ellipsoid->get_curvature_factor()(2);
332 obstacle_list.push_back(std::move(obstacle));
336 return obstacle_list;
345 std::shared_lock l{m_doa.mutex};
348 doa::Agent doa_agent{agent.safety_margin};
350 Eigen::Vector3d agent_global_position = agent.pos.cast<
double>() / 1000;
351 Eigen::Vector3d agent_velocity = agent.desired_vel.cast<
double>() / 1000;
353 if (m_doa.cfg.only_2d)
355 agent_global_position(2) = 0;
356 agent_velocity(2) = 0;
359 doa_agent.set_position(agent_global_position);
360 doa_agent.set_linear_velocity(agent_velocity);
364 Eigen::Vector3d modulated_vel = doa::Modulation::modulate_velocity(doa_agent,
366 m_doa.cfg.local_modulation,
368 m_doa.cfg.planar_modulation,
369 m_doa.cfg.critical_distance,
370 m_doa.cfg.weight_power);
373 modulated_vel *= 1000;
376 if (m_doa.cfg.only_2d)
378 modulated_vel(2) = 0;
383 return modulated_vel.cast<
float>();
390 std::lock_guard l2{m_vis.mutex};
391 std::lock_guard l{m_doa.mutex};
394 std::vector<buffer::update> update_log;
395 std::map<std::string, obstacledetection::Obstacle> obstacles;
397 std::lock_guard l{m_buf.mutex};
399 if (not m_buf.needs_env_update)
405 update_log = m_buf.update_log;
406 obstacles = m_buf.obstacles;
407 m_buf.update_log.clear();
408 m_buf.obstacles.clear();
409 m_buf.needs_env_update =
false;
410 m_buf.last_env_update = IceUtil::Time::now();
417 doa::Environment::iterator it = m_doa.env.find(
update.name);
421 case buffer::update_type::removal:
424 if (it != m_doa.env.end())
426 m_doa.env.erase(
update.name);
431 <<
"obstacle is unknown to the environment.";
437 if (it != m_doa.env.end())
440 m_doa.env.erase(
update.name);
447 const obstacledetection::Obstacle& obstacle = obstacles.at(
update.name);
448 m_doa.env.add_obstacle(::create_doa_obstacle(obstacle));
457 m_vis.needs_update =
true;
460 ARMARX_VERBOSE <<
"Updating environment with " << update_log.size() <<
" updates took "
468 std::shared_lock l{m_doa.mutex};
470 const std::string filename_annotated = getName() +
"_" +
filename;
471 ARMARX_DEBUG <<
"Writing debug plots to `/tmp/" << filename_annotated <<
".png`.";
473 const bool show =
false;
476 doa::PlottingTools::plot_configuration(
477 m_doa.env.get_obstacle_list(), filename_annotated, show, not m_doa.cfg.only_2d);
481 ARMARX_WARNING <<
"Tried to write debug plots but failed, probably because "
482 <<
"`python3-matplotlib` are not installed.";
487 armarx::DSObstacleAvoidance::sanity_check_obstacle(
488 const armarx::obstacledetection::Obstacle& obstacle)
const
490 ARMARX_DEBUG <<
"Sanity checking obstacle `" << obstacle.name <<
"`.";
492 const std::string curvature_error =
"Curvature must be greater than or equal to 1.";
493 const std::string z_comps_set_error =
"2D mode activated. Z-component values must not be set.";
499 if (m_doa.cfg.only_2d)
501 ARMARX_DEBUG <<
"Additionally sanity checking obstacle `" << obstacle.name
502 <<
"` for 2D compliance.";
512 armarx::DSObstacleAvoidance::run_visualization()
517 std::lock_guard l{m_vis.mutex};
518 if (not m_vis.needs_update)
523 Layer layer_obs = arviz.layer(
"obstacles");
524 Layer layer_sms = arviz.layer(
"safety_margins");
525 Layer layer_rps = arviz.layer(
"reference_points");
526 Layer layer_bbs = arviz.layer(
"bounding_boxes");
528 for (
const obstacledetection::Obstacle& obstacle : getObstacles())
533 if (obstacle.name ==
"human")
538 else if (not m_doa.cfg.only_2d)
544 const double safetyMarginZ = m_doa.cfg.only_2d ? 1 : obstacle.safetyMarginZ;
545 const double posZ = m_doa.cfg.only_2d ? 1 : obstacle.posZ;
546 const double refPosZ = m_doa.cfg.only_2d ? 1 : obstacle.refPosZ;
547 const double axisLengthZ = m_doa.cfg.only_2d ? 1 : obstacle.axisLengthZ;
550 simox::math::pos_rpy_to_mat4f(obstacle.posX, obstacle.posY, posZ, 0, 0, obstacle.yaw);
551 const Eigen::Vector3f dim(obstacle.axisLengthX, obstacle.axisLengthY, axisLengthZ);
552 const Eigen::Vector3f sm(obstacle.safetyMarginX, obstacle.safetyMarginY, safetyMarginZ);
553 const Eigen::Vector3f curv(obstacle.curvatureX, obstacle.curvatureY, obstacle.curvatureZ);
555 if (m_doa.cfg.only_2d)
560 .axisLengths(dim.head<2>())
562 .curvature(curv.head<2>())
567 .axisLengths((dim + sm).head<2>())
568 .height(dim(2) + sm(2))
569 .curvature(curv.head<2>())
575 Ellipsoid{obstacle.name}.
pose(pose).axisLengths(dim).curvature(curv).color(color));
579 .axisLengths(dim + sm)
584 layer_rps.
add(
Sphere{obstacle.name +
"_rp"}
585 .
position(Eigen::Vector3f(obstacle.refPosX, obstacle.refPosY, refPosZ))
589 layer_bbs.
add(
Box{obstacle.name +
"_bb"}.
pose(pose).size(dim).color(color));
592 m_vis.needs_update =
false;
594 arviz.commit({layer_obs, layer_sms, layer_rps});
599 armarx::DSObstacleAvoidance::run_watchdog()
601 bool needs_env_update;
602 bool was_recently_updated;
604 std::unique_lock l{m_buf.mutex};
605 needs_env_update = m_buf.needs_env_update;
606 was_recently_updated = IceUtil::Time::now() - m_buf.last_env_update <= m_watchdog.threshold;
609 if (needs_env_update and not was_recently_updated)
611 ARMARX_WARNING <<
"Environment has not been updated for over " << m_watchdog.threshold
612 <<
". Have you forgotten to call "
613 <<
"`updateEnvironment()` after adding, updating or removing obstacles? "
614 <<
"Forcing update now.";
625 def->
optional(m_vis.enabled,
"visualize",
"Enable/disable visualization.");
626 def->optional(m_watchdog.enabled,
"udpate_watchdog",
"Run environment update watchdog.");
627 def->optional(m_doa.load_obstacles_from_file,
628 "load_obstacles_from",
629 "Path to JSON file to load initial obstacles from.");
632 def->optional(m_doa.cfg.only_2d,
"doa.only_2d",
"Only consider 2D.");
633 def->optional(m_doa.cfg.aggregated,
"doa.aggregated",
"Aggregated environment.");
634 def->optional(m_doa.cfg.agent_safety_margin,
"doa.agent_safety_margin",
"Agent safety margin.");
635 def->optional(m_doa.cfg.local_modulation,
"doa.local_modulation",
"Local modulation on/off.");
636 def->optional(m_doa.cfg.repulsion,
"doa.repulsion",
"Repulsion on/off.");
638 m_doa.cfg.planar_modulation,
"doa.planar_modulation",
"Planar modulation on/off.");
639 def->optional(m_doa.cfg.critical_distance,
"doa.critical_distance",
"Critical distance.");
640 def->optional(m_doa.cfg.weight_power,
"doa.weight_power",
"Weight power");