Costmap3D.cpp
Go to the documentation of this file.
1#include "Costmap3D.h"
2
3#include <algorithm>
4#include <cmath>
5#include <cstddef>
6#include <limits>
7#include <optional>
8#include <utility>
9#include <vector>
10
11#include <Eigen/Core>
12#include <Eigen/Geometry>
13
14#include <range/v3/view/zip.hpp>
15
19
22
24{
25
26
28 const Parameters& parameters,
29 const SceneBounds& sceneBounds,
30 const std::optional<Mask>& mask,
31 const core::Pose2D& origin) :
32 grid(grid),
33 mask(mask),
34 sceneBounds(sceneBounds),
35 parameters(parameters),
36 global_T_Costmap3D(origin)
37 {
38 validateSizes();
39 }
40
41 void
42 Costmap3D::validateSizes() const
43 {
44 // Check that the costmap for all orientations has the same size
45 if (grid.size() > 0)
46 {
47 const auto size = getSize();
48 auto rows = size.x();
49 auto columns = size.y();
50 // In case there is a mask, also check if that has the same size
51 if (mask.has_value())
52 {
53 rows = mask->rows();
54 columns = mask->cols();
55 }
56
57 for (const auto& g : grid)
58 {
59 ARMARX_CHECK_EQUAL(g.rows(), rows);
60 ARMARX_CHECK_EQUAL(g.cols(), columns);
61 }
62 }
63 }
64
67 {
69
70 Eigen::Vector2f posLocal;
71 posLocal.x() =
72 sceneBounds.min.x() + index.x() * parameters.cellSize + parameters.cellSize / 2;
73 posLocal.y() =
74 sceneBounds.min.y() + index.y() * parameters.cellSize + parameters.cellSize / 2;
75
76 return posLocal;
77 }
78
81 {
82 const Costmap3D::Position Costmap3D_P_pos = toPositionLocal(index);
83 return global_T_Costmap3D * Costmap3D_P_pos;
84 }
85
86 bool
88 {
89 if (not isWithinRange(index))
90 {
91 return false;
92 }
93
94 if (mask.has_value())
95 {
96 if (not mask.value()(index.x(), index.y()))
97 {
98 ARMARX_DEBUG << "Index masked out (mask)";
99 }
100 return mask.value()(index.x(), index.y());
101 }
102
103 return true;
104 }
105
106 bool
108 {
109 if (index.x() < 0 || index.y() < 0)
110 {
111 ARMARX_VERBOSE << "Index out of bounds (< 0)";
112 return false;
113 }
114
115 if (index.x() >= getSize().x() || index.y() >= getSize().y())
116 {
117 ARMARX_VERBOSE << "Index out of bounds (>= grid size)";
118 return false;
119 }
120 return true;
121 }
122
123 bool
125 {
126 if (mask.has_value())
127 {
128 if (not mask.value()(index.x(), index.y()))
129 {
130 ARMARX_DEBUG << "Index masked out (mask)";
131 }
132 return not mask.value()(index.x(), index.y());
133 }
134 return false;
135 }
136
138 Costmap3D::toVertex(const Eigen::Vector2f& globalPosition) const
139 {
140 const auto localPosition = global_T_Costmap3D.inverse() * globalPosition;
141
142 const float vX = (localPosition.x() - parameters.cellSize / 2 - sceneBounds.min.x()) /
143 parameters.cellSize;
144 const float vY = (localPosition.y() - parameters.cellSize / 2 - sceneBounds.min.y()) /
145 parameters.cellSize;
146
147 const int iX = std::round(vX - 0.01);
148 const int iY = std::round(vY - 0.01);
149
150 const auto size = getSize();
151 const int iXSan = std::clamp<int>(iX, 0, size.x() - 1);
152 const int iYSan = std::clamp<int>(iY, 0, size.y() - 1);
153
154 // FIXME accept one cell off
155
156 // ARMARX_CHECK_GREATER(iX, 0);
157 // ARMARX_CHECK_GREATER(iY, 0);
158
159 // ARMARX_CHECK_LESS_EQUAL(iX, grid.rows() - 1);
160 // ARMARX_CHECK_LESS_EQUAL(iY, grid.cols() - 1);
161
162 return Vertex{.index = Index{iXSan, iYSan}, .position = globalPosition};
163 }
164
167 {
168 const float degrees = (360.F / parameters.orientations) * index;
169 return {.index = index, .degrees = degrees};
170 }
171
172 float
173 rotationFrom0To360(float degrees)
174 {
175 float deg = std::fmod(degrees, 360.F);
176 if (deg < 0.F)
177 {
178 deg += 360.F;
179 }
180 return deg;
181 }
182
183 Costmap3D::Rotation
185 {
186 degrees = rotationFrom0To360(degrees);
187 //const float tmp = (degrees - (180.F / parameters.orientations)) / 360.F; // add half of discretization
188 const float tmp = (degrees + (180.F / parameters.orientations)) /
189 (360.F / parameters.orientations); // normalize to [0, orientations)
190 int index = tmp; // round up
191 index = std::min(index, parameters.orientations - 1);
192 return {.index = index, .degrees = degrees};
193 }
194
195 const SceneBounds&
197 {
198 return sceneBounds;
199 }
200
203 {
205
206 if (mask.has_value())
207 {
208 ARMARX_CHECK_GREATER(mask->array().sum(), 0)
209 << "At least one element has to be valid. Here, all elements are masked out!";
210 }
211
212 // index of the min element
213 Grid::value_type::Index row = 0;
214 Grid::value_type::Index col = 0;
215 const auto size = getSize();
216
217 // value of the min element
218 float minVal = std::numeric_limits<float>::max();
219
220 for (int r = 0; r < size.x(); r++)
221 {
222 for (int c = 0; c < size.y(); c++)
223 {
224 if (mask.has_value())
225 {
226 if (not mask.value()(r, c)) // skip invalid cells
227 {
228 continue;
229 }
230
231 if (rotation.index == ALL_ORIENTATIONS.index)
232 {
233 for (const auto& g : grid)
234 {
235 // min value across all orientations
236 const float currentVal = g(r, c);
237 if (currentVal < minVal)
238 {
239 minVal = currentVal;
240 row = r;
241 col = c;
242 }
243 }
244 }
245 else
246 {
247 const auto& g = grid[rotation.index];
248 // min value across all orientations
249 const float currentVal = g(r, c);
250 if (currentVal < minVal)
251 {
252 minVal = currentVal;
253 row = r;
254 col = c;
255 }
256 }
257 }
258 }
259 }
260
261 return {.value = minVal, .index = {row, col}, .position = toPositionGlobal({row, col})};
262 }
263
265 Costmap3D::params() const noexcept
266 {
267 return parameters;
268 }
269
270 const Costmap3D::Grid&
272 {
273 return grid;
274 }
275
276 bool
277 Costmap3D::isInCollision(const Position& p, std::optional<Rotation> rotation) const
278 {
279 const auto v = toVertex(p);
280 if (rotation.has_value())
281 {
282 return grid.at(rotation->index)(v.index.x(), v.index.y()) <= 0.F;
283 }
284
285 // No rotation given
286 // So check all rotations for collision
287 return std::any_of(grid.begin(),
288 grid.end(),
289 [&v](const auto& grid2d)
290 { return grid2d(v.index.x(), v.index.y()) <= 0.F; });
291 }
292
293 const std::optional<Costmap3D::Mask>&
294 Costmap3D::getMask() const noexcept
295 {
296 return mask;
297 }
298
299 std::optional<Costmap3D::Mask>&
301 {
302 return mask;
303 }
304
305 float
307 {
308 return grid.at(rot_index)(index.x(), index.y());
309 }
310
311 std::optional<float>
313 {
314 if (not isValid(index))
315 {
316 ARMARX_IMPORTANT << "Requested index " << index << " but it is masked out!";
317 return std::nullopt;
318 }
319 if (rot_index < 0 || rot_index > this->grid.size() - 1)
320 {
321 ARMARX_IMPORTANT << "Requested rotation index " << rot_index
322 << " but it is not within allowed range (0 .. "
323 << this->grid.size() - 1 << ")";
324 return std::nullopt;
325 }
326
327 return grid.at(rot_index)(index.x(), index.y());
328 }
329
330 std::optional<float>
331 Costmap3D::value(const Position& position,
332 const Costmap3D::RotationDegrees rotation_degrees) const
333 {
334 ARMARX_DEBUG << "value ...";
335
336 const auto v = toVertex(position);
337 const auto r = closestRotationFromDegrees(rotation_degrees);
338 return value(v.index, r.index);
339 }
340
341 void
343 {
345
346 const auto [min, max] = getValidBoundingBox();
347
348 // calculate new scene bounds based on bounding box
349 const auto newSceneBounds = SceneBounds{
350 .min = sceneBounds.min + parameters.cellSize * Eigen::Vector2f{min.cast<float>()},
351 .max = sceneBounds.min + parameters.cellSize * Eigen::Vector2f{max.cast<float>()}};
352
353 ARMARX_VERBOSE << "Cutting grid to valid bounding box: " << min << " and " << max;
354
355 // update grids
356 for (auto& grid2d : grid)
357 {
358 const auto newGrid =
359 grid2d.block(min.x(), min.y(), max.x() - min.x() + 1, max.y() - min.y() + 1);
360 grid2d = newGrid;
361 }
362
363
364 if (mask.has_value())
365 {
366 ARMARX_VERBOSE << "Cutting mask to valid bounding box: " << min << " and " << max;
367
368 const auto newMask =
369 mask->block(min.x(), min.y(), max.x() - min.x() + 1, max.y() - min.y() + 1);
370 mask = newMask;
371 }
372
373 // update scene bounds
374 sceneBounds.min = newSceneBounds.min;
375 sceneBounds.max = newSceneBounds.max;
376
377 ARMARX_VERBOSE << "New scene bounds: " << sceneBounds.min << " and " << sceneBounds.max;
378 }
379
380 std::size_t
382 {
383 if (not mask.has_value())
384 {
385 const auto size = getSize();
386 return size.y() * size.x();
387 }
388
389 return mask->array().cast<int>().sum();
390 }
391
393 Costmap3D::findClosestValidIndex(const Costmap3D::Index& index)
394 {
396
397 if (isValid(index))
398 {
399 return index;
400 }
401
402 const auto x = index.x();
403 const auto y = index.y();
404
405 // Check all cells in a radius around the given index
406 for (int r = 1; r < 10; r++)
407 {
408 for (int i = -r; i <= r; i++)
409 {
410 for (int j = -r; j <= r; j++)
411 {
412 const auto idx = Costmap3D::Index{x + i, y + j};
413 if (isValid(idx))
414 {
415 return idx;
416 }
417 }
418 }
419 }
420
421 ARMARX_ERROR << "No valid index found in the vicinity of " << index;
422 return index;
423 }
424
425 std::pair<Costmap3D::Index, Costmap3D::Index>
427 {
428 if (not mask.has_value())
429 {
430 return {Index{0, 0}, getSize() - Index{1, 1}};
431 }
432
433 ARMARX_VERBOSE << "Fraction of valid elements: "
434 << mask->cast<float>().sum() / mask->size();
435
437
438 const Index size = getSize();
439 Index min = getSize();
440 Index max = Index{0, 0};
441
442 for (int x = 0; x < size.x(); x++)
443 {
444 for (int y = 0; y < size.y(); y++)
445 {
446 if (mask.value()(x, y))
447 {
448 min.x() = std::min(min.x(), x);
449 min.y() = std::min(min.y(), y);
450 max.x() = std::max(max.x(), x);
451 max.y() = std::max(max.y(), y);
452 }
453 }
454 }
455
456 return {min, max};
457 }
458
461 {
462 return {grid[0].rows(), grid[0].cols()};
463 }
464
465 Costmap
466 Costmap3D::costmapForOrientation(RotationIndex rotationIndex, bool initializeMask) const
467 {
468 Costmap c{grid[rotationIndex],
470 .binaryGrid = false,
471 .cellSize = parameters.cellSize,
472 .robotRadius = 0.F,
473 .sceneBoundsMargin = parameters.sceneBoundsMargin,
474 },
475 sceneBounds};
476 // c.getMutableGrid().array() += 2000.0;
477 if (initializeMask)
478 {
479 c.getMutableMask() = c.getGrid().array() > 0.0F;
480 }
481 return c;
482 }
483
486 const Grid& grid,
487 const std::optional<Mask>& mask)
488 {
489 return {grid, other.params(), other.getLocalSceneBounds(), mask, other.origin()};
490 }
491
492 void
494 {
495 for (int rotIdx = 0; rotIdx < parameters.orientations; rotIdx++)
496 {
497 ARMARX_INFO << "Converting to pseudo SDF for rotation index " << rotIdx;
499 }
500
501 // remove mask
502 mask.reset();
503 }
504
505 void
507 {
509 // Idea: Apply the mask, remove the mask and then do flood-fill to generate negative cell values for nodes in collision
510 // For the filling, we use a list of nodes in the masked-out region bordering the un-masked region as seeds
511 // We can then calculate a (negative) distance value based on the neighboring un-masked cell values
512 if (not mask.has_value())
513 {
514 ARMARX_INFO << "Costmap3D::convertToPseudoSDF: No mask present, nothing to do.";
515 return;
516 }
517
518 // copy mask to local variable for easier access
519 Costmap3D::Mask workingMask{this->getMask().value()};
520
521 std::vector<Costmap3D::Index> borderingIndices;
522 std::vector<Costmap3D::Index> nextBorderingIndices;
523
524 const auto isValid = [this, rotationIndex](const Costmap3D::Index& idx)
525 {
526 if (this->isWithinRange(idx) && this->value_ignore_mask(idx, rotationIndex) == 0.0)
527 {
528 return false;
529 }
530
531 return this->isValid(idx);
532 };
533
534 // first fill of borderingIndices, idea: go through all nodes and see if they are masked out and are bordering one that is not
535 for (int x = 0; x < getSize().x(); x++)
536 {
537 for (int y = 0; y < getSize().y(); y++)
538 {
539 const Costmap3D::Index idx{x, y};
540 if (not isValid(idx))
541 {
542 workingMask(x, y) = false;
543
544 // check neighbors
545 const std::vector<Costmap3D::Index> neighbors = {
546 {x + 1, y}, {x - 1, y}, {x, y + 1}, {x, y - 1}};
547 for (const auto& nIdx : neighbors)
548 {
549 if (isValid(nIdx))
550 {
551 borderingIndices.push_back(idx);
552 break;
553 }
554 }
555 }
556 }
557 }
558
559 ARMARX_INFO << "Found " << borderingIndices.size()
560 << " bordering indices to start flood-fill.";
561 const float cellSize = parameters.cellSize;
562 // now do flood-fill
563 int iteration = 0;
564 while (not borderingIndices.empty())
565 {
566 ARMARX_DEBUG << "Flood-fill iteration " << iteration
567 << ", current border size: " << borderingIndices.size();
568 for (const auto& idx : borderingIndices)
569 {
570 if (workingMask(idx.x(), idx.y()))
571 {
572 // already processed
573 continue;
574 }
575
576 float minNeighborValue = std::numeric_limits<float>::min();
577 // check neighbors
578 const std::vector<Costmap3D::Index> neighbors = {{idx.x() + 1, idx.y()},
579 {idx.x() - 1, idx.y()},
580 {idx.x(), idx.y() + 1},
581 {idx.x(), idx.y() - 1}};
582 for (const auto& nIdx : neighbors)
583 {
584 if (isWithinRange(nIdx) && workingMask(nIdx.x(), nIdx.y()))
585 {
586 const float neighborValue = value_ignore_mask(nIdx, rotationIndex);
587 if (neighborValue != 0.0 && neighborValue > minNeighborValue)
588 {
589 minNeighborValue = neighborValue;
590 }
591 }
592 }
593 // set value to negative distance
594 //const float newValue = minNeighborValue - cellSize;
595 const float newValue = -1.0F * iteration * cellSize;
596 grid[rotationIndex](idx.x(), idx.y()) = newValue;
597 // mark as valid now
598 workingMask(idx.x(), idx.y()) = true;
599 // add neighbors to nextBorderingIndices
600 for (const auto& nIdx : neighbors)
601 {
602 if (isWithinRange(nIdx) && not workingMask(nIdx.x(), nIdx.y()))
603 {
604 nextBorderingIndices.push_back(nIdx);
605 }
606 }
607 }
608 borderingIndices = nextBorderingIndices;
609 nextBorderingIndices.clear();
610 iteration++;
611 }
612
613 // check if sum of mask == num cells
614 if (workingMask.array().cast<int>().sum(), getSize().x() * getSize().y())
615 {
616 ARMARX_WARNING << "After flood-fill, not all cells are valid!";
617 }
618 }
619} // namespace armarx::navigation::algorithms::orientation_aware
uint8_t index
constexpr T c
Costmap costmapForOrientation(RotationIndex rotationIndex, bool initializeMask=false) const
bool isWithinRange(const Index &index) const noexcept
const SceneBounds & getLocalSceneBounds() const noexcept
static Costmap3D WithSameDimsAs(const Costmap3D &other, const Grid &grid, const std::optional< Mask > &mask=std::nullopt)
Create a Costmap3D with the same dimensions and parameters as the given one.
Costmap3D(const Grid &grid, const Parameters &parameters, const SceneBounds &sceneBounds, const std::optional< Mask > &mask=std::nullopt, const core::Pose2D &origin=core::Pose2D::Identity())
Definition Costmap3D.cpp:27
bool isInCollision(const Position &p, std::optional< Rotation > rotation=std::nullopt) const
const std::optional< Mask > & getMask() const noexcept
bool isMaskedOut(const Index &index) const noexcept
Eigen::Matrix< bool, Eigen::Dynamic, Eigen::Dynamic > Mask
Definition Costmap3D.h:61
Rotation rotationFromIndex(RotationIndex index) const
Rotation closestRotationFromDegrees(RotationDegrees degrees) const
std::optional< Costmap3D::Mask > & getMutableMask() noexcept
std::optional< float > value(const Index &index, const RotationIndex rot_index) const
Vertex toVertex(const Position &globalPosition) const
bool isValid(const Index &index) const noexcept
checks whether the cell is masked out
Definition Costmap3D.cpp:87
Optimum optimum(Rotation rotation=ALL_ORIENTATIONS) const
float value_ignore_mask(const Index &index, const RotationIndex rot_index) const
#define ARMARX_CHECK_GREATER(lhs, rhs)
This macro evaluates whether lhs is greater (>) than rhs and if it turns out to be false it will thro...
#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_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#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
Eigen::Isometry2f Pose2D
Definition basic_types.h:34
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::vector< T > max(const std::vector< T > &v1, const std::vector< T > &v2)
std::vector< T > min(const std::vector< T > &v1, const std::vector< T > &v2)
#define ARMARX_TRACE
Definition trace.h:77