util.cpp
Go to the documentation of this file.
1/**
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @author Fabian Reister ( fabian dot reister at kit dot edu )
17 * @date 2022
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
22#include "util.h"
23
24#include <algorithm>
25#include <cstddef>
26#include <cstdint>
27#include <optional>
28#include <vector>
29
30#include <opencv2/core/eigen.hpp>
31#include <opencv2/core/mat.hpp>
32#include <opencv2/core/types.hpp>
33#include <opencv2/imgproc.hpp>
34
35#include <VirtualRobot/BoundingBox.h>
36#include <VirtualRobot/CollisionDetection/CollisionModel.h> // IWYU pragma: keep
37#include <VirtualRobot/Random.h>
38#include <VirtualRobot/Robot.h> // IWYU pragma: keep
39#include <VirtualRobot/SceneObjectSet.h> // IWYU pragma: keep
40#include <VirtualRobot/VirtualRobot.h>
41#include <VirtualRobot/Workspace/WorkspaceGrid.h>
42
47
53
54#include <range/v3/algorithm/for_each.hpp>
55#include <range/v3/view/zip.hpp>
56
58{
59
60
62 computeSceneBounds(const VirtualRobot::SceneObjectSetPtr& obstacles,
63 const std::vector<VirtualRobot::RobotPtr>& articulatedObjects,
64 const SceneBounds& init,
65 const std::vector<Room>& rooms,
66 const float margin,
67 const bool restrictToRooms)
68 {
69 SceneBounds bounds = init;
70
71 if (restrictToRooms)
72 {
73 ARMARX_INFO << "Computing scene bounds from rooms";
74
76
77 for (const auto& room : rooms)
78 {
79 for (const auto& point : room.polygon)
80 {
81 bounds.min.x() = std::min(point.x(), bounds.min.x());
82 bounds.min.y() = std::min(point.y(), bounds.min.y());
83 bounds.max.x() = std::max(point.x(), bounds.max.x());
84 bounds.max.y() = std::max(point.y(), bounds.max.y());
85 }
86 }
87
88 return bounds;
89 }
90
91 // if no rooms are given, use the obstacles and articulated objects
92 {
93
94 ARMARX_CHECK_NOT_NULL(obstacles);
95
96 ARMARX_CHECK((not obstacles->getCollisionModels().empty()) or
97 (not articulatedObjects.empty()));
98
99 const auto expandBounds = [&bounds](const VirtualRobot::BoundingBox&& bb)
100 {
101 if (!bb.getMin().allFinite() || !bb.getMax().allFinite())
102 {
103 // non-finite bounding box, e.g. from an object with a missing collision
104 // mesh - ignore it rather than poisoning the scene bounds with NaN
105 return;
106 }
107 bounds.min.x() = std::min(bb.getMin().x(), bounds.min.x());
108 bounds.min.y() = std::min(bb.getMin().y(), bounds.min.y());
109 bounds.max.x() = std::max(bb.getMax().x(), bounds.max.x());
110 bounds.max.y() = std::max(bb.getMax().y(), bounds.max.y());
111 };
112
113 // non-articulated objects
114 for (const auto& colModel : obstacles->getCollisionModels())
115 {
116 expandBounds(colModel->getBoundingBox());
117 }
118
119 ARMARX_VERBOSE << "non-articulated objects: scene bounds: " << bounds.min << " and "
120 << bounds.max;
121
122 // articulated objects
123 for (const auto& articulatedObject : articulatedObjects)
124 {
125 if (articulatedObject->getCollisionModels().empty())
126 {
127 ARMARX_WARNING << "The articulated object `" << articulatedObject->getType()
128 << "/" << articulatedObject->getName()
129 << "` does not provide any collision model!";
130 }
131
132 ARMARX_DEBUG << "Articulated object `" << articulatedObject->getType() << "/"
133 << articulatedObject->getName();
134
135 for (const auto& colModel : articulatedObject->getCollisionModels())
136 {
137 expandBounds(colModel->getBoundingBox());
138 ARMARX_DEBUG << VAROUT(colModel->getBoundingBox().getMin()) << " and "
139 << VAROUT(colModel->getBoundingBox().getMax());
140 ARMARX_DEBUG << bounds.min << " and " << bounds.max;
141 }
142 }
143
144 ARMARX_VERBOSE << "articulated objects: scene bounds: " << bounds.min << " and "
145 << bounds.max;
146 }
147
148 // expand bounds by margin
149 bounds.min.x() -= margin;
150 bounds.min.y() -= margin;
151 bounds.max.x() += margin;
152 bounds.max.y() += margin;
153
154 return bounds;
155 }
156
157 SceneBounds
158 computeSceneBounds(const std::vector<Eigen::Vector2f>& points,
159 const SceneBounds& init,
160 const float margin)
161 {
162 SceneBounds bounds = init;
163
164 for (const auto& point : points)
165 {
166 bounds.min.x() = std::min(point.x(), bounds.min.x());
167 bounds.min.y() = std::min(point.y(), bounds.min.y());
168 bounds.max.x() = std::max(point.x(), bounds.max.x());
169 bounds.max.y() = std::max(point.y(), bounds.max.y());
170 }
171
172 // expand bounds by margin
173 bounds.min.x() -= margin;
174 bounds.min.y() -= margin;
175 bounds.max.x() += margin;
176 bounds.max.y() += margin;
177
178 return bounds;
179 }
180
181 SceneBounds
182 toSceneBounds(const VirtualRobot::WorkspaceGrid::Extends& extends)
183 {
184 return {.min = Eigen::Vector2f{extends.minX, extends.minY},
185 .max = Eigen::Vector2f{extends.maxX, extends.maxY}};
186 }
187
188 SceneBounds
189 merge(const std::vector<SceneBounds>& sceneBounds)
190 {
191 SceneBounds bounds;
192
193 const auto expandBounds = [&bounds](const SceneBounds& sb)
194 {
195 bounds.min.x() = std::min(bounds.min.x(), sb.min.x());
196 bounds.min.y() = std::min(bounds.min.y(), sb.min.y());
197
198 bounds.max.x() = std::max(bounds.max.x(), sb.max.x());
199 bounds.max.y() = std::max(bounds.max.y(), sb.max.y());
200
201 return bounds;
202 };
203
204 ranges::for_each(sceneBounds, expandBounds);
205
206 return bounds;
207 }
208
209 Costmap
210 toCostmap(const VirtualRobot::WorkspaceGrid& workspaceGrid)
211 {
212 const Costmap::Grid grid(workspaceGrid.getCells().x, workspaceGrid.getCells().y);
213 const SceneBounds sceneBounds = toSceneBounds(workspaceGrid.getExtends());
214
215 const Costmap::Parameters parameters{.binaryGrid = false,
216 .cellSize = workspaceGrid.getDiscretizeSize()};
217
218 return {grid, parameters, sceneBounds};
219 }
220
221 // Costmap
222 // mergeUnaligned(const std::vector<Costmap>& costmaps,
223 // const std::vector<float>& weights,
224 // float cellSize,
225 // const Eigen::Array2f& offset)
226 // {
227 // ARMARX_TRACE;
228
229 // ARMARX_CHECK_NOT_EMPTY(costmaps);
230 // ARMARX_CHECK_EQUAL(costmaps.size(), weights.size());
231
232 // // if unset, use the smallest cell size
233 // if (cellSize < 0)
234 // {
235 // const auto compCellSize = [](const Costmap& a, const Costmap& b) -> bool
236 // { return a.params().cellSize < b.params().cellSize; };
237
238 // cellSize = ranges::min_element(costmaps, compCellSize)->params().cellSize;
239 // }
240
241 // // scale each costmap
242 // std::vector<Costmap> scaledCostmaps =
243 // simox::alg::apply(costmaps,
244 // [&cellSize](const Costmap& costmap) -> Costmap
245 // { return scaleCostmap(costmap, cellSize); });
246
247 // // merge costmaps into one
248 // ARMARX_VERBOSE << "Merging " << scaledCostmaps.size() << " costmaps";
249
250 // // - combine scene bounds
251 // std::vector<SceneBounds> sceneBoundsAll = simox::alg::apply(
252 // scaledCostmaps, [](const Costmap& costmap) { return costmap.getSceneBounds(); });
253
254 // SceneBounds sceneBounds = merge(sceneBoundsAll);
255 // sceneBounds.min.x() -= 0.5 * cellSize;
256 // sceneBounds.max.x() -= 0.5 * cellSize;
257 // sceneBounds.min.y() -= cellSize;
258 // sceneBounds.max.y() -= cellSize;
259
260
261 // sceneBounds.min.x() += offset.x();
262 // sceneBounds.max.x() += offset.x();
263 // sceneBounds.min.y() += offset.y();
264 // sceneBounds.max.y() += offset.y();
265
266 // // - create grid
267 // const auto largeGrid = CostmapBuilder::createUniformGrid(
268 // sceneBounds, Costmap::Parameters{.binaryGrid = false, .cellSize = cellSize});
269
270 // Costmap largeCostmap(
271 // largeGrid, Costmap::Parameters{.binaryGrid = false, .cellSize = cellSize}, sceneBounds);
272
273 // // TODO save each combo individually
274
275 // static int i = 0;
276 // // - add all costmaps to this one
277 // ranges::for_each(ranges::views::zip(scaledCostmaps, weights),
278 // [&largeCostmap](const auto& p)
279 // {
280 // ARMARX_INFO << "adding to costmap";
281 // const auto& [costmap, weight] = p;
282 // largeCostmap.add(costmap, weight);
283
284 // armarx::navigation::algorithms::save(
285 // costmap, "/tmp/costmap" + std::to_string(i++));
286 // });
287
288 // armarx::navigation::algorithms::save(largeCostmap,
289 // "/tmp/large-costmap" + std::to_string(i++));
290
291 // return largeCostmap;
292 // }
293
294 void
295 checkSameSize(const std::vector<Costmap>& costmaps)
296 {
297 ARMARX_CHECK_NOT_EMPTY(costmaps);
298
299 const auto assertSameSize = [&costmaps](const Costmap& costmap)
300 {
301 ARMARX_CHECK_EQUAL(costmap.getGrid().rows(), costmaps.front().getGrid().rows());
302 ARMARX_CHECK_EQUAL(costmap.getGrid().rows(), costmaps.front().getGrid().rows());
303 };
304
305 ranges::for_each(costmaps, assertSameSize);
306 }
307
308 Costmap
309 mergeAligned(const std::vector<Costmap>& costmaps, CostmapMergeMode mode)
310 {
311 ARMARX_CHECK_NOT_EMPTY(costmaps);
312 checkSameSize(costmaps);
313
314
315 // average mode: fixed weight
316 if (mode == CostmapMergeMode::AVERAGE)
317 {
318 const std::vector<float> weights(costmaps.size(), 1.F / costmaps.size());
319 return mergeAligned(costmaps, weights);
320 }
321
322 const auto grid = CostmapBuilder::createUniformGrid(costmaps.front().getLocalSceneBounds(),
323 costmaps.front().params());
324
325 Costmap mergedCostmap = Costmap::WithSameDimsAs(costmaps.front(), grid);
326
327 const auto addMode = [&]() -> Costmap::AddMode
328 {
329 Costmap::AddMode addMode{};
330
331 switch (mode)
332 {
334 ARMARX_CHECK(false) << "This case should already be handled.";
335 break;
337 addMode = Costmap::AddMode::MIN;
338 break;
340 addMode = Costmap::AddMode::MAX;
341 break;
342 }
343
344 return addMode;
345 }();
346
347 ranges::for_each(costmaps,
348 [&mergedCostmap, &addMode](const auto& costmap)
349 { mergedCostmap.add(costmap, addMode); });
350
351 return mergedCostmap;
352 }
353
354 Costmap
355 mergeAligned(const std::vector<Costmap>& costmaps, const std::vector<float>& weights)
356 {
357 ARMARX_CHECK_EQUAL(costmaps.size(), weights.size());
358 ARMARX_CHECK_NOT_EMPTY(costmaps);
359 checkSameSize(costmaps);
360
361
362 const auto grid = CostmapBuilder::createUniformGrid(costmaps.front().getLocalSceneBounds(),
363 costmaps.front().params());
364
365 Costmap mergedCostmap = Costmap::WithSameDimsAs(costmaps.front(), grid);
366
367 // foreach pair (costmap, weight): add it to there merged costmap
368 ranges::for_each(ranges::views::zip(costmaps, weights),
369 [&mergedCostmap](const auto& p)
370 {
371 const auto& [costmap, weight] = p;
372 mergedCostmap.add(costmap, weight);
373 });
374
375 return mergedCostmap;
376 }
377
378 Costmap
379 scaleCostmap(const Costmap& costmap, float cellSize)
380 {
381 const float scale = costmap.params().cellSize / cellSize;
382 ARMARX_VERBOSE << "Scaling grid by a factor of " << scale;
383
384 cv::Mat src;
385 cv::eigen2cv(costmap.getGrid(), src);
386
387 cv::Mat dst;
388 cv::resize(src, dst, cv::Size{0, 0}, scale, scale);
389
390 Eigen::MatrixXf scaledGrid;
391 cv::cv2eigen(dst, scaledGrid);
392
393
394 std::optional<Costmap::Mask> scaledMask;
395 if (costmap.getMask().has_value())
396 {
398 ARMARX_VERBOSE << "Scaling mask by a factor of " << scale;
399
400 // just an int large enough to check if resizing causes
401 // issues on boundary to invalid regions
402 constexpr int someInt = 100;
403
405 someInt * costmap.getMask().value().cast<std::uint8_t>();
406
407 cv::Mat maskMat;
408 cv::eigen2cv(mask, maskMat);
409
410 cv::Mat maskScaled;
411 cv::resize(maskMat, maskScaled, cv::Size{0, 0}, scale, scale);
412
413 // only those points are valid where the original value '10' is preserved.
414 // all other points are boundary points to the invalid area
415 // cv::threshold(maskMat, maskMat, 10.9, 1, cv::THRESH_BINARY);
416
417 Eigen::MatrixXi scaledMaskInt; //(scaledGrid.rows(), scaledGrid.cols());
418 cv::cv2eigen(maskScaled, scaledMaskInt);
419
420 Costmap::Mask m = scaledMaskInt.array() >= someInt;
421
422 scaledMask = m;
423 }
424
425 ARMARX_VERBOSE << "Original size (" << costmap.getGrid().rows() << ", "
426 << costmap.getGrid().cols() << ")";
427 ARMARX_VERBOSE << "Resized to (" << scaledGrid.rows() << ", " << scaledGrid.cols() << ")";
428
429 return {scaledGrid,
430 Costmap::Parameters{.binaryGrid = false, .cellSize = cellSize},
431 costmap.getLocalSceneBounds(),
432 scaledMask,
433 costmap.origin()};
434 }
435
436 std::optional<core::Pose2D>
438 {
439 const auto sizeX = costmap.getGrid().cols();
440 const auto sizeY = costmap.getGrid().rows();
441
442 constexpr std::size_t maxIterations = 1000;
443
444 // sample a valid pose in the costmap
445
446 for (std::size_t iteration = 0; iteration < maxIterations; iteration++)
447 {
448 const float iX = VirtualRobot::RandomFloat() * static_cast<float>(sizeX);
449 const float iY = VirtualRobot::RandomFloat() * static_cast<float>(sizeY);
450
451 algorithms::Costmap::Index idx(iX, iY);
452
453 if (not costmap.isValid(idx))
454 {
455 continue;
456 }
457
458 core::Pose2D pose = core::Pose2D::Identity();
459 pose.translation() = costmap.toPositionGlobal(idx);
460 return pose;
461 }
462
463 ARMARX_ERROR << "Failed to sample pose in costmap!";
464 return std::nullopt;
465 }
466
467 static void
468 invalidateOutsideRoomsImpl(
469 const std::vector<Room>& rooms,
470 std::function<Costmap::Position(const Costmap::Index&)> indexToGlobal,
471 Costmap::Mask& mask,
472 const float footprintRadius)
473 {
474 const std::size_t c_x = mask.rows();
475 const std::size_t c_y = mask.cols();
476
477 const auto isInsideRoom = [&rooms, footprintRadius](const Eigen::Vector2f& pos) -> bool
478 {
479 return std::any_of(rooms.begin(),
480 rooms.end(),
481 [&pos, footprintRadius](const Room& room) -> bool
482 { return room.isInside(pos, footprintRadius); });
483 };
484
485 for (unsigned int x = 0; x < c_x; x++)
486 {
487 for (unsigned int y = 0; y < c_y; y++)
488 {
489 const Costmap::Index index{x, y};
490 const Costmap::Position position = indexToGlobal(index);
491
492 if (not isInsideRoom(position))
493 {
494 mask(x, y) = false;
495 }
496 }
497 }
498 }
499
500 void
501 invalidateOutsideRooms(const std::vector<Room>& rooms,
503 const float footprintRadius)
504 {
505 ARMARX_CHECK(costmap.getMutableMask().has_value());
506 const auto indexToGlobal = [&costmap](auto idx) { return costmap.toPositionGlobal(idx); };
507
508 invalidateOutsideRoomsImpl(
509 rooms, indexToGlobal, costmap.getMutableMask().value(), -1.F);
510
511 // make sure small gaps between rooms are included
512 costmap.getMutableMask() =
513 dilateMask(costmap.getMask().value(),
514 static_cast<int>(std::max(
515 1.F, costmap.params().sceneBoundsMargin / costmap.params().cellSize)));
516
517 // apply the footprint erosion last so the gap dilation does not undo it
518 if (footprintRadius > 0.F)
519 {
520 invalidateOutsideRoomsImpl(
521 rooms, indexToGlobal, costmap.getMutableMask().value(), footprintRadius);
522 }
523 }
524
525 void
526 invalidateOutsideRooms(const std::vector<Room>& rooms,
528 const float footprintRadius)
529 {
530 ARMARX_CHECK(costmap.getMutableMask().has_value());
531 invalidateOutsideRoomsImpl(
532 rooms,
533 [&costmap](auto idx) { return costmap.toPositionGlobal(idx); },
534 costmap.getMutableMask().value(),
535 footprintRadius);
536 }
537
539 dilateMask(const Costmap::Mask& mask, int numIterations)
540 {
541 cv::Mat cvMask;
542 cv::eigen2cv(mask, cvMask);
543
544 cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
545 cv::dilate(cvMask, cvMask, kernel, cv::Point(-1, -1), numIterations);
546
547 Costmap::Mask dst;
548 cv::cv2eigen(cvMask, dst);
549 return dst;
550 }
551
552} // namespace armarx::navigation::algorithms
uint8_t index
#define ARMARX_CHECK_NOT_EMPTY(c)
#define VAROUT(x)
static Eigen::MatrixXf createUniformGrid(const SceneBounds &sceneBounds, const Costmap::Parameters &parameters)
static Costmap WithSameDimsAs(const Costmap &other, const Grid &grid, const std::optional< Mask > &mask=std::nullopt)
Create a costmap with the same dimensions and parameters as the given one.
Definition Costmap.cpp:854
Eigen::Matrix< bool, Eigen::Dynamic, Eigen::Dynamic > Mask
Definition Costmap.h:60
bool add(const Costmap &other, float weight=1.0)
Definition Costmap.cpp:376
#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_CHECK_EQUAL(lhs, rhs)
This macro evaluates whether lhs is equal (==) rhs and if it turns out to be false it will throw an E...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:185
This file is part of ArmarX.
Costmap::Mask dilateMask(const Costmap::Mask &mask, int numIterations)
Definition util.cpp:539
Costmap toCostmap(const VirtualRobot::WorkspaceGrid &workspaceGrid)
Definition util.cpp:210
SceneBounds merge(const std::vector< SceneBounds > &sceneBounds)
Definition util.cpp:189
Costmap scaleCostmap(const Costmap &costmap, float cellSize)
Definition util.cpp:379
std::optional< core::Pose2D > sampleValidPositionInMap(const algorithms::Costmap &costmap)
Definition util.cpp:437
void invalidateOutsideRooms(const std::vector< Room > &rooms, Costmap &costmap, const float footprintRadius)
Definition util.cpp:501
CostmapMergeMode
Defines how the.
Definition util.h:72
SceneBounds computeSceneBounds(const VirtualRobot::SceneObjectSetPtr &obstacles, const std::vector< VirtualRobot::RobotPtr > &articulatedObjects, const SceneBounds &init, const std::vector< Room > &rooms, const float margin, const bool restrictToRooms)
Definition util.cpp:62
SceneBounds toSceneBounds(const VirtualRobot::WorkspaceGrid::Extends &extends)
Definition util.cpp:182
Costmap mergeAligned(const std::vector< Costmap > &costmaps, CostmapMergeMode mode)
Definition util.cpp:309
void checkSameSize(const std::vector< Costmap > &costmaps)
Definition util.cpp:295
Eigen::Isometry2f Pose2D
Definition basic_types.h:34
This file offers overloads of toIce() and fromIce() functions for STL container types.
#define ARMARX_TRACE
Definition trace.h:75