ArvizIntrospector.cpp
Go to the documentation of this file.
1#include "ArvizIntrospector.h"
2
3#include <algorithm>
4#include <cstddef>
5#include <iterator>
6#include <optional>
7#include <string>
8#include <utility>
9#include <vector>
10
11#include <Eigen/Geometry>
12
13#include <range/v3/algorithm/max_element.hpp>
14
15#include <SimoxUtility/algorithm/apply.hpp>
16#include <SimoxUtility/algorithm/get_map_keys_values.h>
17#include <SimoxUtility/algorithm/string/string_tools.h>
18#include <SimoxUtility/color/Color.h>
19#include <SimoxUtility/color/ColorMap.h>
20#include <SimoxUtility/color/cmaps/colormaps.h>
21#include <VirtualRobot/Robot.h> // IWYU pragma: keep
22#include <VirtualRobot/VirtualRobot.h>
23
29
37
44
46{
47
48 namespace
49 {
50 /// How far apart the markers drawn along a path are. The trajectory carries one point
51 /// per parametrization sample -- 500 of them -- which is the resolution the solver
52 /// wants, not the resolution an observer can read.
53 constexpr float markerSpacing = 200.F; // [mm]
54
55 /**
56 * @brief Indices into `points`, spaced at least `spacing` apart along the path.
57 *
58 * The first and the last point are always kept: the start and the goal are the two an
59 * observer looks for, and the last one carries the final velocity.
60 */
61 std::vector<std::size_t>
62 subsample(const std::vector<core::GlobalTrajectoryPoint>& points, const float spacing)
63 {
64 if (points.size() < 2)
65 {
66 return points.empty() ? std::vector<std::size_t>{} : std::vector<std::size_t>{0};
67 }
68
69 std::vector<std::size_t> indices{0};
70 float sinceLast = 0.F;
71
72 for (std::size_t i = 1; i + 1 < points.size(); i++)
73 {
74 sinceLast += (points.at(i).waypoint.pose.translation() -
75 points.at(i - 1).waypoint.pose.translation())
76 .norm();
77
78 if (sinceLast >= spacing)
79 {
80 indices.push_back(i);
81 sinceLast = 0.F;
82 }
83 }
84
85 indices.push_back(points.size() - 1);
86
87 return indices;
88 }
89 } // namespace
90
91 inline armarx::PackagePath
92 asPackagePath(const std::string& absfilepath)
93 {
94 const std::vector<std::string> packages =
96 const std::string package = armarx::ArmarXDataPath::getProject(packages, absfilepath);
97
98 // make sure that the relative path is without the 'package/' prefix
99 const std::string relPath = [&absfilepath, &package]() -> std::string
100 {
101 if (simox::alg::starts_with(absfilepath, package))
102 {
103 // remove "package" + "/"
104 return absfilepath.substr(package.size() + 1, -1);
105 }
106
107 return absfilepath;
108 }();
109
110 return {package, relPath};
111 }
112
114 const VirtualRobot::RobotConstPtr& robot,
115 const objpose::ObjectPoseClient& objClient) :
116 arviz(arviz), robot(robot), objClient(objClient)
117 // visualization(arviz,
118 // util::Visualization::Params{.robotModelFileName = asPackagePath(robot->getFilename())})
119 {
120 robotPosesLayer = arviz.layer("robot_poses");
121 }
122
123 // TODO maybe run with predefined frequency instead of
124
125 void
127 {
128 ARMARX_DEBUG << "ArvizIntrospector::onGlobalPlannerResult";
129
130 drawGlobalTrajectory(result.trajectory);
131 if (result.helperTrajectory)
132 {
133 drawGlobalHelperTrajectory(result.helperTrajectory.value());
134 }
135 else
136 {
137 // clear layer
138 auto layer = arviz.layer("global_helper_trajectory");
139 layers[layer.data_.name] = std::move(layer);
140 }
141 arviz.commit(simox::alg::get_values(layers));
142
143
144 // visualization.visualize(result.trajectory);
145 }
146
147 void
149 {
150 ARMARX_DEBUG << "ArvizIntrospector::onGlobalPlannerSubdivision";
151
152 drawGlobalPathSubdivision(subdivision);
153
154 if (subdivision.plan.helperTrajectory)
155 {
156 drawGlobalHelperTrajectory(subdivision.plan.helperTrajectory.value());
157 }
158 else
159 {
160 // clear layer
161 auto layer = arviz.layer("global_helper_trajectory");
162 layers[layer.data_.name] = std::move(layer);
163 }
164
165 arviz.commit(simox::alg::get_values(layers));
166 }
167
168 void
170 const std::optional<local_planning::LocalPlannerResult>& result)
171 {
172 if (result)
173 {
174 drawLocalTrajectory(result.value().trajectory);
175 }
176 else
177 {
178 drawLocalTrajectory(std::nullopt);
179 }
180
181 arviz.commit(simox::alg::get_values(layers));
182 }
183
184 // void
185 // ArvizIntrospector::onTrajectoryControllerResult(
186 // const traj_ctrl::TrajectoryControllerResult& result)
187 // {
188 // std::lock_guard g{mtx};
189
190 // drawRawVelocity(result.twist);
191 // }
192
193 // void
194 // ArvizIntrospector::onSafetyGuardResult(const safety_guard::SafetyGuardResult& result)
195 // {
196 // std::lock_guard g{mtx};
197
198 // drawSafeVelocity(result.twist);
199 // }
200
201 void
203 {
204 auto layer = arviz.layer("goal");
205 layer.add(viz::Pose("goal").pose(goal).scale(3));
206
207 // A new goal starts a new travelled path -- otherwise the polyline would jump from
208 // wherever the last request ended to wherever this one starts.
209 travelled.clear();
210 lastPose.reset();
211 robotPosesLayer.clear();
212
213 arviz.commit({layer, robotPosesLayer});
214
215 // visualization.setTarget(goal);
216 }
217
218 void
220 {
221 // Recorded finer than it is marked: the polyline is only as faithful to what the robot
222 // did as the samples behind it, while the orientation markers are for reading.
223 constexpr float recordingSpacing = 50.F; // [mm]
224
225 if (lastPose and (lastPose->translation() - pose.translation()).norm() < recordingSpacing)
226 {
227 return;
228 }
229
230 lastPose = pose;
231 travelled.push_back(pose);
232
233 // Rebuilt rather than appended to: the path is one element covering every sample so far,
234 // so it has to replace its predecessor. This runs once per `recordingSpacing` of motion,
235 // not per control cycle.
236 robotPosesLayer.clear();
237
238 std::vector<core::Position> positions;
239 positions.reserve(travelled.size());
240 std::transform(travelled.begin(),
241 travelled.end(),
242 std::back_inserter(positions),
243 [](const core::Pose& p) -> core::Position { return p.translation(); });
244
245 robotPosesLayer.add(
246 viz::Path("travelled").points(positions).color(viz::Color::orange()).width(10));
247
248 const auto markerStride =
249 static_cast<std::size_t>(std::max(1.F, markerSpacing / recordingSpacing));
250
251 for (std::size_t i = 0; i < travelled.size(); i += markerStride)
252 {
253 robotPosesLayer.add(viz::Pose("pose" + std::to_string(i)).pose(travelled.at(i)));
254 }
255
256 arviz.commit(robotPosesLayer);
257 }
258
259 void
260 ArvizIntrospector::onGlobalShortestPath(const std::vector<core::Pose>& path)
261 {
262 auto layer = arviz.layer("graph_shortest_path");
263
264 const auto toPosition = [](const core::Pose& pose) -> core::Position
265 { return pose.translation(); };
266
267
268 std::vector<core::Position> pts;
269 pts.reserve(path.size());
270 std::transform(path.begin(), path.end(), std::back_inserter(pts), toPosition);
271
272
273 // layer.add(viz::Path("shortest_path").points(pts).color(viz::Color::purple()));
274
275 for (size_t i = 0; i < (pts.size() - 1); i++)
276 {
277 layer.add(viz::Arrow("segment_" + std::to_string(i))
278 .fromTo(pts.at(i), pts.at(i + 1))
279 .color(viz::Color::purple()));
280 }
281
282
283 arviz.commit(layer);
284 }
285
286 // private methods
287
288
289 void
290 ArvizIntrospector::drawGlobalTrajectory(const core::GlobalTrajectory& trajectory)
291 {
292 drawGlobalTrajectory(trajectory, "global_planner", simox::Color::blue());
293 }
294
295 void
296 ArvizIntrospector::drawGlobalHelperTrajectory(const core::GlobalTrajectory& trajectory)
297 {
298 drawGlobalTrajectory(trajectory, "global_helper_trajectory", simox::Color::gray());
299 }
300
301 void
302 ArvizIntrospector::drawGlobalTrajectory(const core::GlobalTrajectory& trajectory,
303 const std::string layerName,
304 simox::color::Color color)
305 {
306 auto layer = arviz.layer(layerName);
307
308 layer.add(viz::Path("path").points(trajectory.positions()).color(color));
309
310 const auto cmap = simox::color::cmaps::viridis();
311
312 const float maxVelocity = ranges::max_element(trajectory.points(),
313 std::less{},
315 ->velocity;
316
317
318 for (const std::size_t idx : subsample(trajectory.points(), markerSpacing))
319 {
320 const core::GlobalTrajectoryPoint& tp = trajectory.points().at(idx);
321 const float scale = tp.velocity;
322
323 const Eigen::Vector3f target =
324 scale * tp.waypoint.pose.linear() * Eigen::Vector3f::UnitY();
325
326 layer.add(
327 viz::Arrow("velocity_" + std::to_string(idx))
328 .fromTo(tp.waypoint.pose.translation(), tp.waypoint.pose.translation() + target)
329 .color(cmap.at(tp.velocity / maxVelocity)));
330 }
331
332 layers[layer.data_.name] = std::move(layer);
333 }
334
335 void
336 ArvizIntrospector::drawGlobalPathSubdivision(const GlobalPathSubdivision& subdivision)
337 {
338 if (subdivision.subdivision.empty())
339 {
340 // no actual subdivision
341 drawGlobalTrajectory(subdivision.plan.trajectory);
342 return;
343 }
344
345 auto layer = arviz.layer("global_path_subdivision");
346
347 for (const auto& segment : subdivision.subdivision)
348 {
349 viz::Path path("segment_" + std::to_string(segment.s));
350 // subTrajectory from [s,t] to overlap with next segment
351 const auto& subTrajectory = subdivision.plan.trajectory.getSubTrajectory(
352 segment.s, std::min(segment.t + 1, subdivision.plan.trajectory.points().size()));
353 path.points(subTrajectory.positions());
354 layer.add(
355 path.color(segment.useLocalPlanner ? simox::Color::blue() : simox::Color::red()));
356 }
357
358 const auto cmap = simox::color::cmaps::viridis();
359 const float maxVelocity = ranges::max_element(subdivision.plan.trajectory.points(),
360 std::less{},
362 ->velocity;
363
364 for (const std::size_t idx :
365 subsample(subdivision.plan.trajectory.points(), markerSpacing))
366 {
367 const core::GlobalTrajectoryPoint& tp = subdivision.plan.trajectory.points().at(idx);
368 const float scale = tp.velocity;
369
370 const Eigen::Vector3f target =
371 scale * tp.waypoint.pose.linear() * Eigen::Vector3f::UnitY();
372
373 layer.add(
374 viz::Arrow("velocity_" + std::to_string(idx))
375 .fromTo(tp.waypoint.pose.translation(), tp.waypoint.pose.translation() + target)
376 .color(cmap.at(tp.velocity / maxVelocity)));
377 }
378
379 layers[layer.data_.name] = std::move(layer);
380 }
381
382 void
383 ArvizIntrospector::drawLocalTrajectory(const std::optional<core::LocalTrajectory>& input)
384 {
385 if (!input)
386 {
387 // no local trajectory found, remove old one
388 auto layer = arviz.layer("local_planner");
389 auto velLayer = arviz.layer("local_planner_velocity");
390
391 layers[layer.data_.name] = std::move(layer);
392 layers[velLayer.data_.name] = std::move(velLayer);
393 return;
394 }
395
396 const auto trajectory = input.value();
397
398 auto layer = arviz.layer("local_planner");
399
400 const std::vector<Eigen::Vector3f> points =
401 simox::alg::apply(trajectory.points(),
402 [](const core::LocalTrajectoryPoint& pt) -> Eigen::Vector3f
403 { return pt.pose.translation(); });
404
405 layer.add(viz::Path("path").points(points).color(simox::Color::green()));
406
407
408 // Visualize trajectory speed
409 auto velLayer = arviz.layer("local_planner_velocity");
410
411 simox::ColorMap cm = simox::color::cmaps::inferno();
412 cm.set_vmin(0);
413 cm.set_vmax(0.6);
414
415 for (size_t i = 0; i < trajectory.points().size() - 1; i++)
416 {
417 const core::LocalTrajectoryPoint start = trajectory.points().at(i);
418 const core::LocalTrajectoryPoint end = trajectory.points().at(i + 1);
419
420 const Duration dT = end.timestamp - start.timestamp;
421 const Eigen::Vector3f distance = end.pose.translation() - start.pose.translation();
422 const float speed = distance.norm() / 1000 / dT.toSecondsDouble();
423
424 const Eigen::Vector3f pos = start.pose.translation() + distance / 2;
425 const simox::Color color = cm.at(speed);
426
427 velLayer.add(
428 viz::Sphere("velocity_" + std::to_string(i)).position(pos).radius(50).color(color));
429 }
430
431 layers[layer.data_.name] = std::move(layer);
432 layers[velLayer.data_.name] = std::move(velLayer);
433 }
434
435 void
436 ArvizIntrospector::drawRawVelocity(const core::Twist& twist)
437 {
438 auto layer = arviz.layer("trajectory_controller");
439
440 layer.add(viz::Arrow("linear_velocity")
441 .fromTo(robot->getGlobalPosition(),
442 core::Pose(robot->getGlobalPose()) * twist.linear)
443 .color(simox::Color::orange()));
444
445 layers[layer.data_.name] = std::move(layer);
446 }
447
448 void
449 ArvizIntrospector::drawSafeVelocity(const core::Twist& twist)
450 {
451 auto layer = arviz.layer("safety_guard");
452
453 layer.add(viz::Arrow("linear_velocity")
454 .fromTo(robot->getGlobalPosition(),
455 core::Pose(robot->getGlobalPose()) * twist.linear)
456 .color(simox::Color::green()));
457
458 layers[layer.data_.name] = std::move(layer);
459 }
460
462 arviz{other.arviz},
463 robot{other.robot},
464 layers(std::move(other.layers)),
465 lastPose(other.lastPose),
466 travelled(std::move(other.travelled)),
467 // Not moving this left the moved-to introspector with a default-constructed layer,
468 // whose component and name are empty -- so every `commit` of it published nothing the
469 // GUI could show. The navigator move-constructs its introspector on connect, which is
470 // why the travelled path was implemented but never appeared.
471 robotPosesLayer(std::move(other.robotPosesLayer))
472 // visualization(std::move(other.visualization))
473 {
474 }
475
476 void
478 {
479 // clear all layers
480 for (auto& [name, layer] : layers)
481 {
482 layer.markForDeletion();
483 }
484 arviz.commit(simox::alg::get_values(layers));
485 layers.clear();
486
487 // some special internal layers of TEB
488 arviz.commitDeleteLayer("local_planner_obstacles");
489 arviz.commitDeleteLayer("local_planner_velocity");
490 arviz.commitDeleteLayer("local_planner_path_alternatives");
491 }
492
495 {
496 return *this;
497 }
498
499 void
501 {
502 auto layer = arviz.layer("global_planning_graph");
503
504 const objpose::ObjectPoseMap objects = objClient.fetchObjectPosesAsMap();
505 const std::vector<ObjectInfo> info = objClient.getObjectFinder().findAllObjects();
506
507 for (const auto& edge : graph.edges())
508 {
509
510 const auto sourcePose = graph.vertex(edge.sourceDescriptor()).attrib().getPose();
511 const auto targetPose = graph.vertex(edge.targetDescriptor()).attrib().getPose();
512
513
514 const viz::Color color = [&]()
515 {
516 if (edge.attrib().cost() > 100'000) // "NaN check"
517 {
518 return viz::Color::red();
519 }
520
521 return viz::Color::green();
522 }();
523
524 const auto from = core::resolveLocation(objects, info, sourcePose);
525 const auto to = core::resolveLocation(objects, info, sourcePose);
526
527 if (from.pose.has_value() && to.pose.has_value())
528 {
529 layer.add(
530 viz::Arrow(std::to_string(edge.sourceObjectID().t) + " -> " +
531 std::to_string(edge.targetObjectID().t))
532 .fromTo(from.pose.value().translation(), to.pose.value().translation())
533 .color(color));
534 }
535 }
536
537 arviz.commit(layer);
538 }
539
540 void
542 {
543 fn(arviz);
544 }
545
546 void
548 {
549 clear();
550
551 // visualization.success();
552 }
553
554 void
556 {
557 clear();
558
559 // visualization.failed();
560 }
561
562
563} // namespace armarx::navigation::server
static std::vector< std::string > FindAllArmarXSourcePackages()
double toSecondsDouble() const
Returns the amount of seconds.
Definition Duration.cpp:90
void onRobotPose(const core::Pose &pose) override
ArvizIntrospector(armarx::viz::Client arviz, const VirtualRobot::RobotConstPtr &robot, const objpose::ObjectPoseClient &objClient)
void onGlobalShortestPath(const std::vector< core::Pose > &path) override
void onGlobalGraph(const core::Graph &graph) override
void onGlobalPlannerResult(const global_planning::GlobalPlannerResult &result) override
ArvizIntrospector & operator=(ArvizIntrospector &&) noexcept
void callGenericDrawFunction(std::function< void(viz::Client &)>) override
void onGlobalPlannerSubdivision(const GlobalPathSubdivision &subdivision) override
void onGoal(const core::Pose &goal) override
void onLocalPlannerResult(const std::optional< local_planning::LocalPlannerResult > &result) override
Provides access to the armarx::objpose::ObjectPoseStorageInterface (aka the object memory).
DerivedT & color(Color color)
Definition ElementOps.h:218
Layer layer(std::string const &name) const override
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
armarx::core::time::Duration Duration
void resolveLocation(Graph::Vertex &vertex, const aron::data::DictPtr &locationData)
Definition Graph.cpp:267
Eigen::Isometry3f Pose
Definition basic_types.h:31
Eigen::Vector3f Position
Definition basic_types.h:36
This file is part of ArmarX.
Definition Visu.h:48
This file is part of ArmarX.
armarx::PackagePath asPackagePath(const std::string &absfilepath)
std::map< ObjectID, ObjectPose > ObjectPoseMap
Vertex target(const detail::edge_base< Directed, Vertex > &e, const PCG &)
pcl::PointIndices::Ptr indices(const PCG &g)
Retrieve the indices of the points of the point cloud stored in a point cloud graph that actually bel...
double distance(const Point &a, const Point &b)
Definition point.hpp:95
std::optional< core::GlobalTrajectory > helperTrajectory
Optional helper trajectory that can be used for visualization or debugging purposes.
global_planning::GlobalPlannerResult plan
Definition Navigator.h:79
Arrow & fromTo(const Eigen::Vector3f &from, const Eigen::Vector3f &to)
Definition Elements.h:219
void add(ElementT const &element)
Definition Layer.h:31