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 bounds.min.x() = std::min(bb.getMin().x(), bounds.min.x());
102 bounds.min.y() = std::min(bb.getMin().y(), bounds.min.y());
103 bounds.max.x() = std::max(bb.getMax().x(), bounds.max.x());
104 bounds.max.y() = std::max(bb.getMax().y(), bounds.max.y());
105 };
106
107 // non-articulated objects
108 for (const auto& colModel : obstacles->getCollisionModels())
109 {
110 expandBounds(colModel->getBoundingBox());
111 }
112
113 ARMARX_VERBOSE << "non-articulated objects: scene bounds: " << bounds.min << " and "
114 << bounds.max;
115
116 // articulated objects
117 for (const auto& articulatedObject : articulatedObjects)
118 {
119 if (articulatedObject->getCollisionModels().empty())
120 {
121 ARMARX_WARNING << "The articulated object `" << articulatedObject->getType()
122 << "/" << articulatedObject->getName()
123 << "` does not provide any collision model!";
124 }
125
126 ARMARX_DEBUG << "Articulated object `" << articulatedObject->getType() << "/"
127 << articulatedObject->getName();
128
129 for (const auto& colModel : articulatedObject->getCollisionModels())
130 {
131 expandBounds(colModel->getBoundingBox());
132 ARMARX_DEBUG << VAROUT(colModel->getBoundingBox().getMin()) << " and "
133 << VAROUT(colModel->getBoundingBox().getMax());
134 ARMARX_DEBUG << bounds.min << " and " << bounds.max;
135 }
136 }
137
138 ARMARX_VERBOSE << "articulated objects: scene bounds: " << bounds.min << " and "
139 << bounds.max;
140 }
141
142 // expand bounds by margin
143 bounds.min.x() -= margin;
144 bounds.min.y() -= margin;
145 bounds.max.x() += margin;
146 bounds.max.y() += margin;
147
148 return bounds;
149 }
150
151 SceneBounds
152 computeSceneBounds(const std::vector<Eigen::Vector2f>& points,
153 const SceneBounds& init,
154 const float margin)
155 {
156 SceneBounds bounds = init;
157
158 for (const auto& point : points)
159 {
160 bounds.min.x() = std::min(point.x(), bounds.min.x());
161 bounds.min.y() = std::min(point.y(), bounds.min.y());
162 bounds.max.x() = std::max(point.x(), bounds.max.x());
163 bounds.max.y() = std::max(point.y(), bounds.max.y());
164 }
165
166 // expand bounds by margin
167 bounds.min.x() -= margin;
168 bounds.min.y() -= margin;
169 bounds.max.x() += margin;
170 bounds.max.y() += margin;
171
172 return bounds;
173 }
174
175 SceneBounds
176 toSceneBounds(const VirtualRobot::WorkspaceGrid::Extends& extends)
177 {
178 return {.min = Eigen::Vector2f{extends.minX, extends.minY},
179 .max = Eigen::Vector2f{extends.maxX, extends.maxY}};
180 }
181
182 SceneBounds
183 merge(const std::vector<SceneBounds>& sceneBounds)
184 {
185 SceneBounds bounds;
186
187 const auto expandBounds = [&bounds](const SceneBounds& sb)
188 {
189 bounds.min.x() = std::min(bounds.min.x(), sb.min.x());
190 bounds.min.y() = std::min(bounds.min.y(), sb.min.y());
191
192 bounds.max.x() = std::max(bounds.max.x(), sb.max.x());
193 bounds.max.y() = std::max(bounds.max.y(), sb.max.y());
194
195 return bounds;
196 };
197
198 ranges::for_each(sceneBounds, expandBounds);
199
200 return bounds;
201 }
202
203 Costmap
204 toCostmap(const VirtualRobot::WorkspaceGrid& workspaceGrid)
205 {
206 const Costmap::Grid grid(workspaceGrid.getCells().x, workspaceGrid.getCells().y);
207 const SceneBounds sceneBounds = toSceneBounds(workspaceGrid.getExtends());
208
209 const Costmap::Parameters parameters{.binaryGrid = false,
210 .cellSize = workspaceGrid.getDiscretizeSize()};
211
212 return {grid, parameters, sceneBounds};
213 }
214
215 // Costmap
216 // mergeUnaligned(const std::vector<Costmap>& costmaps,
217 // const std::vector<float>& weights,
218 // float cellSize,
219 // const Eigen::Array2f& offset)
220 // {
221 // ARMARX_TRACE;
222
223 // ARMARX_CHECK_NOT_EMPTY(costmaps);
224 // ARMARX_CHECK_EQUAL(costmaps.size(), weights.size());
225
226 // // if unset, use the smallest cell size
227 // if (cellSize < 0)
228 // {
229 // const auto compCellSize = [](const Costmap& a, const Costmap& b) -> bool
230 // { return a.params().cellSize < b.params().cellSize; };
231
232 // cellSize = ranges::min_element(costmaps, compCellSize)->params().cellSize;
233 // }
234
235 // // scale each costmap
236 // std::vector<Costmap> scaledCostmaps =
237 // simox::alg::apply(costmaps,
238 // [&cellSize](const Costmap& costmap) -> Costmap
239 // { return scaleCostmap(costmap, cellSize); });
240
241 // // merge costmaps into one
242 // ARMARX_VERBOSE << "Merging " << scaledCostmaps.size() << " costmaps";
243
244 // // - combine scene bounds
245 // std::vector<SceneBounds> sceneBoundsAll = simox::alg::apply(
246 // scaledCostmaps, [](const Costmap& costmap) { return costmap.getSceneBounds(); });
247
248 // SceneBounds sceneBounds = merge(sceneBoundsAll);
249 // sceneBounds.min.x() -= 0.5 * cellSize;
250 // sceneBounds.max.x() -= 0.5 * cellSize;
251 // sceneBounds.min.y() -= cellSize;
252 // sceneBounds.max.y() -= cellSize;
253
254
255 // sceneBounds.min.x() += offset.x();
256 // sceneBounds.max.x() += offset.x();
257 // sceneBounds.min.y() += offset.y();
258 // sceneBounds.max.y() += offset.y();
259
260 // // - create grid
261 // const auto largeGrid = CostmapBuilder::createUniformGrid(
262 // sceneBounds, Costmap::Parameters{.binaryGrid = false, .cellSize = cellSize});
263
264 // Costmap largeCostmap(
265 // largeGrid, Costmap::Parameters{.binaryGrid = false, .cellSize = cellSize}, sceneBounds);
266
267 // // TODO save each combo individually
268
269 // static int i = 0;
270 // // - add all costmaps to this one
271 // ranges::for_each(ranges::views::zip(scaledCostmaps, weights),
272 // [&largeCostmap](const auto& p)
273 // {
274 // ARMARX_INFO << "adding to costmap";
275 // const auto& [costmap, weight] = p;
276 // largeCostmap.add(costmap, weight);
277
278 // armarx::navigation::algorithms::save(
279 // costmap, "/tmp/costmap" + std::to_string(i++));
280 // });
281
282 // armarx::navigation::algorithms::save(largeCostmap,
283 // "/tmp/large-costmap" + std::to_string(i++));
284
285 // return largeCostmap;
286 // }
287
288 void
289 checkSameSize(const std::vector<Costmap>& costmaps)
290 {
291 ARMARX_CHECK_NOT_EMPTY(costmaps);
292
293 const auto assertSameSize = [&costmaps](const Costmap& costmap)
294 {
295 ARMARX_CHECK_EQUAL(costmap.getGrid().rows(), costmaps.front().getGrid().rows());
296 ARMARX_CHECK_EQUAL(costmap.getGrid().rows(), costmaps.front().getGrid().rows());
297 };
298
299 ranges::for_each(costmaps, assertSameSize);
300 }
301
302 Costmap
303 mergeAligned(const std::vector<Costmap>& costmaps, CostmapMergeMode mode)
304 {
305 ARMARX_CHECK_NOT_EMPTY(costmaps);
306 checkSameSize(costmaps);
307
308
309 // average mode: fixed weight
310 if (mode == CostmapMergeMode::AVERAGE)
311 {
312 const std::vector<float> weights(costmaps.size(), 1.F / costmaps.size());
313 return mergeAligned(costmaps, weights);
314 }
315
316 const auto grid = CostmapBuilder::createUniformGrid(costmaps.front().getLocalSceneBounds(),
317 costmaps.front().params());
318
319 Costmap mergedCostmap = Costmap::WithSameDimsAs(costmaps.front(), grid);
320
321 const auto addMode = [&]() -> Costmap::AddMode
322 {
323 Costmap::AddMode addMode{};
324
325 switch (mode)
326 {
328 ARMARX_CHECK(false) << "This case should already be handled.";
329 break;
331 addMode = Costmap::AddMode::MIN;
332 break;
334 addMode = Costmap::AddMode::MAX;
335 break;
336 }
337
338 return addMode;
339 }();
340
341 ranges::for_each(costmaps,
342 [&mergedCostmap, &addMode](const auto& costmap)
343 { mergedCostmap.add(costmap, addMode); });
344
345 return mergedCostmap;
346 }
347
348 Costmap
349 mergeAligned(const std::vector<Costmap>& costmaps, const std::vector<float>& weights)
350 {
351 ARMARX_CHECK_EQUAL(costmaps.size(), weights.size());
352 ARMARX_CHECK_NOT_EMPTY(costmaps);
353 checkSameSize(costmaps);
354
355
356 const auto grid = CostmapBuilder::createUniformGrid(costmaps.front().getLocalSceneBounds(),
357 costmaps.front().params());
358
359 Costmap mergedCostmap = Costmap::WithSameDimsAs(costmaps.front(), grid);
360
361 // foreach pair (costmap, weight): add it to there merged costmap
362 ranges::for_each(ranges::views::zip(costmaps, weights),
363 [&mergedCostmap](const auto& p)
364 {
365 const auto& [costmap, weight] = p;
366 mergedCostmap.add(costmap, weight);
367 });
368
369 return mergedCostmap;
370 }
371
372 Costmap
373 scaleCostmap(const Costmap& costmap, float cellSize)
374 {
375 const float scale = costmap.params().cellSize / cellSize;
376 ARMARX_VERBOSE << "Scaling grid by a factor of " << scale;
377
378 cv::Mat src;
379 cv::eigen2cv(costmap.getGrid(), src);
380
381 cv::Mat dst;
382 cv::resize(src, dst, cv::Size{0, 0}, scale, scale);
383
384 Eigen::MatrixXf scaledGrid;
385 cv::cv2eigen(dst, scaledGrid);
386
387
388 std::optional<Costmap::Mask> scaledMask;
389 if (costmap.getMask().has_value())
390 {
392 ARMARX_VERBOSE << "Scaling mask by a factor of " << scale;
393
394 // just an int large enough to check if resizing causes
395 // issues on boundary to invalid regions
396 constexpr int someInt = 100;
397
399 someInt * costmap.getMask().value().cast<std::uint8_t>();
400
401 cv::Mat maskMat;
402 cv::eigen2cv(mask, maskMat);
403
404 cv::Mat maskScaled;
405 cv::resize(maskMat, maskScaled, cv::Size{0, 0}, scale, scale);
406
407 // only those points are valid where the original value '10' is preserved.
408 // all other points are boundary points to the invalid area
409 // cv::threshold(maskMat, maskMat, 10.9, 1, cv::THRESH_BINARY);
410
411 Eigen::MatrixXi scaledMaskInt; //(scaledGrid.rows(), scaledGrid.cols());
412 cv::cv2eigen(maskScaled, scaledMaskInt);
413
414 Costmap::Mask m = scaledMaskInt.array() >= someInt;
415
416 scaledMask = m;
417 }
418
419 ARMARX_VERBOSE << "Original size (" << costmap.getGrid().rows() << ", "
420 << costmap.getGrid().cols() << ")";
421 ARMARX_VERBOSE << "Resized to (" << scaledGrid.rows() << ", " << scaledGrid.cols() << ")";
422
423 return {scaledGrid,
424 Costmap::Parameters{.binaryGrid = false, .cellSize = cellSize},
425 costmap.getLocalSceneBounds(),
426 scaledMask,
427 costmap.origin()};
428 }
429
430 std::optional<core::Pose2D>
432 {
433 const auto sizeX = costmap.getGrid().cols();
434 const auto sizeY = costmap.getGrid().rows();
435
436 constexpr std::size_t maxIterations = 1000;
437
438 // sample a valid pose in the costmap
439
440 for (std::size_t iteration = 0; iteration < maxIterations; iteration++)
441 {
442 const float iX = VirtualRobot::RandomFloat() * static_cast<float>(sizeX);
443 const float iY = VirtualRobot::RandomFloat() * static_cast<float>(sizeY);
444
445 algorithms::Costmap::Index idx(iX, iY);
446
447 if (not costmap.isValid(idx))
448 {
449 continue;
450 }
451
452 core::Pose2D pose = core::Pose2D::Identity();
453 pose.translation() = costmap.toPositionGlobal(idx);
454 return pose;
455 }
456
457 ARMARX_ERROR << "Failed to sample pose in costmap!";
458 return std::nullopt;
459 }
460
461 static void
462 invalidateOutsideRoomsImpl(
463 const std::vector<Room>& rooms,
464 std::function<Costmap::Position(const Costmap::Index&)> indexToGlobal,
465 Costmap::Mask& mask)
466 {
467 const std::size_t c_x = mask.rows();
468 const std::size_t c_y = mask.cols();
469
470 const auto isInsideRoom = [&rooms](const Eigen::Vector2f& pos) -> bool
471 {
472 return std::any_of(rooms.begin(),
473 rooms.end(),
474 [&pos](const Room& room) -> bool { return room.isInside(pos); });
475 };
476
477 for (unsigned int x = 0; x < c_x; x++)
478 {
479 for (unsigned int y = 0; y < c_y; y++)
480 {
481 const Costmap::Index index{x, y};
482 const Costmap::Position position = indexToGlobal(index);
483
484 if (not isInsideRoom(position))
485 {
486 mask(x, y) = false;
487 }
488 }
489 }
490 }
491
492 void
493 invalidateOutsideRooms(const std::vector<Room>& rooms, Costmap& costmap)
494 {
495 ARMARX_CHECK(costmap.getMutableMask().has_value());
496 invalidateOutsideRoomsImpl(
497 rooms,
498 [&costmap](auto idx) { return costmap.toPositionGlobal(idx); },
499 costmap.getMutableMask().value());
500
501 // make sure small gaps between rooms are included
502 costmap.getMutableMask() =
503 dilateMask(costmap.getMask().value(),
504 static_cast<int>(std::max(
505 1.F, costmap.params().sceneBoundsMargin / costmap.params().cellSize)));
506 }
507
508 void
510 {
511 ARMARX_CHECK(costmap.getMutableMask().has_value());
512 invalidateOutsideRoomsImpl(
513 rooms,
514 [&costmap](auto idx) { return costmap.toPositionGlobal(idx); },
515 costmap.getMutableMask().value());
516 }
517
519 dilateMask(const Costmap::Mask& mask, int numIterations)
520 {
521 cv::Mat cvMask;
522 cv::eigen2cv(mask, cvMask);
523
524 cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
525 cv::dilate(cvMask, cvMask, kernel, cv::Point(-1, -1), numIterations);
526
527 Costmap::Mask dst;
528 cv::cv2eigen(cvMask, dst);
529 return dst;
530 }
531
532} // 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:181
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
This file is part of ArmarX.
Costmap::Mask dilateMask(const Costmap::Mask &mask, int numIterations)
Definition util.cpp:519
Costmap toCostmap(const VirtualRobot::WorkspaceGrid &workspaceGrid)
Definition util.cpp:204
SceneBounds merge(const std::vector< SceneBounds > &sceneBounds)
Definition util.cpp:183
Costmap scaleCostmap(const Costmap &costmap, float cellSize)
Definition util.cpp:373
std::optional< core::Pose2D > sampleValidPositionInMap(const algorithms::Costmap &costmap)
Definition util.cpp:431
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:176
void invalidateOutsideRooms(const std::vector< Room > &rooms, Costmap &costmap)
Definition util.cpp:493
Costmap mergeAligned(const std::vector< Costmap > &costmaps, CostmapMergeMode mode)
Definition util.cpp:303
void checkSameSize(const std::vector< Costmap > &costmaps)
Definition util.cpp:289
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:77