Skip to content

Commit

Permalink
Rework occupancy grids (#188)
Browse files Browse the repository at this point in the history
Signed-off-by: Michel Hidalgo <[email protected]>
  • Loading branch information
hidmic authored May 20, 2023
1 parent 818439c commit 640c23d
Show file tree
Hide file tree
Showing 20 changed files with 1,235 additions and 543 deletions.
42 changes: 22 additions & 20 deletions beluga/include/beluga/algorithm/raycasting.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@ namespace beluga {

/// Castable 2D ray.
/**
* \tparam Grid A 2D grid
* \tparam OccupancyGrid A 2D occupancy grid
* \tparam Algorithm A callable type, taking start and end
* grid cells for a ray and returning the full trace.
*/
template <class Grid, typename Algorithm = Bresenham2i>
template <class OccupancyGrid, typename Algorithm = Bresenham2i>
class Ray2d {
public:
/// Constructs 2D ray with default ray tracing algorithm.
/**
* See Ray2d(const Grid &, Algorithm, const Sophus::SE2d&, double)
* See Ray2d(const OccupancyGrid &, Algorithm, const Sophus::SE2d&, double)
* for further reference on constructor arguments.
*/
explicit Ray2d(const Grid& grid, const Sophus::SE2d& source_pose, double max_range) noexcept
explicit Ray2d(const OccupancyGrid& grid, const Sophus::SE2d& source_pose, double max_range) noexcept
: Ray2d(grid, Algorithm{}, source_pose, max_range) {}

/// Constructs 2D ray with an specific ray tracing algorithm.
Expand All @@ -59,10 +59,15 @@ class Ray2d {
* same frame as that on which the `grid` origin is defined.
* \param max_range Maximum range for the ray, in meters.
*/
explicit Ray2d(const Grid& grid, Algorithm algorithm, const Sophus::SE2d& source_pose, double max_range) noexcept
explicit Ray2d(
const OccupancyGrid& grid,
Algorithm algorithm,
const Sophus::SE2d& source_pose,
double max_range) noexcept
: grid_(grid),
algorithm_(std::move(algorithm)),
source_pose_in_grid_frame_(grid_.origin().inverse() * source_pose),
source_pose_in_local_frame_(grid_.origin().inverse() * source_pose),
source_cell_(grid_.cell_near(source_pose_in_local_frame_.translation())),
max_range_(max_range) {}

/// Computes ray trace along a given direction.
Expand All @@ -75,11 +80,10 @@ class Ray2d {
const auto far_end_pose_in_source_frame = Sophus::SE2d{
Sophus::SO2d{0.},
Eigen::Vector2d{max_range_ * bearing.unit_complex().x(), max_range_ * bearing.unit_complex().y()}};
const auto far_end_pose_in_grid_frame = source_pose_in_grid_frame_ * far_end_pose_in_source_frame;
const auto start_cell = grid_.cell(source_pose_in_grid_frame_.translation());
const auto end_cell = grid_.cell(far_end_pose_in_grid_frame.translation());
const auto cell_is_valid = [this](const auto& cell) { return grid_.valid(cell); };
return algorithm_(start_cell, end_cell) | ranges::views::take_while(cell_is_valid);
const auto far_end_pose_in_local_frame = source_pose_in_local_frame_ * far_end_pose_in_source_frame;
const auto far_end_cell = grid_.cell_near(far_end_pose_in_local_frame.translation());
const auto cell_is_valid = [this](const auto& cell) { return grid_.contains(cell); };
return algorithm_(source_cell_, far_end_cell) | ranges::views::take_while(cell_is_valid);
}

/// Casts ray along a given direction.
Expand All @@ -90,24 +94,22 @@ class Ray2d {
* \return Distance in meters to first non free cell hit by the ray, if any.
*/
[[nodiscard]] std::optional<double> cast(const Sophus::SO2d& bearing) const {
const auto is_free = [this](const auto& cell) {
// TODO(hidmic): move to occupancy grid API
return Grid::Traits::is_free(grid_.data()[grid_.index(cell)]);
};
for (const auto& cell : trace(bearing)) {
if (!is_free(cell)) {
const auto start_cell = grid_.cell(source_pose_in_grid_frame_.translation());
const auto distance = (grid_.point(cell) - grid_.point(start_cell)).norm();
if (!grid_.free_at(cell)) {
const auto source_position = grid_.coordinates_at(source_cell_);
const auto cell_position = grid_.coordinates_at(cell);
const auto distance = (cell_position - source_position).norm();
return std::make_optional(std::min(distance, max_range_));
}
}
return std::nullopt;
}

private:
const Grid& grid_;
const OccupancyGrid& grid_;
const Algorithm algorithm_;
const Sophus::SE2d source_pose_in_grid_frame_;
const Sophus::SE2d source_pose_in_local_frame_;
const Eigen::Vector2i source_cell_;
const double max_range_;
};

Expand Down
32 changes: 13 additions & 19 deletions beluga/include/beluga/sensor/beam_model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <beluga/algorithm/raycasting.hpp>

#include <range/v3/range/conversion.hpp>
#include <range/v3/view/all.hpp>
#include <range/v3/view/transform.hpp>
#include <sophus/se2.hpp>
Expand Down Expand Up @@ -65,7 +66,7 @@ struct BeamModelParam {
*
* \tparam Mixin The mixed-in type with no particular requirements.
* \tparam OccupancyGrid Type representing an occupancy grid.
* It must satisfy \ref OccupancyGrid2dPage.
* It must satisfy \ref OccupancyGrid2Page.
*/
template <class Mixin, class OccupancyGrid>
class BeamSensorModel : public Mixin {
Expand All @@ -89,8 +90,13 @@ class BeamSensorModel : public Mixin {
* \param ...rest Arguments that are not used by this part of the mixin, but by others.
*/
template <class... Args>
explicit BeamSensorModel(const param_type& params, const OccupancyGrid& grid, Args&&... rest)
: Mixin(std::forward<Args>(rest)...), grid_{grid}, free_cells_{make_free_cells_vector(grid)}, params_{params} {}
explicit BeamSensorModel(const param_type& params, OccupancyGrid grid, Args&&... rest)
: Mixin(std::forward<Args>(rest)...),
params_{params},
grid_{std::move(grid)},
free_states_{
grid_.coordinates_for(grid_.free_cells(), OccupancyGrid::Frame::kGlobal) |
ranges::to<std::vector<Eigen::Vector2d>>} {}

// TODO(ivanpauno): is sensor model the best place for this?
// Maybe the map could be provided by a different part of the mixin,
Expand All @@ -108,9 +114,8 @@ class BeamSensorModel : public Mixin {
*/
template <class Generator>
[[nodiscard]] state_type make_random_state(Generator& gen) const {
auto index_distribution = std::uniform_int_distribution<std::size_t>{0, free_cells_.size() - 1};
return Sophus::SE2d{
Sophus::SO2d::sampleUniform(gen), grid_.origin() * grid_.point(free_cells_[index_distribution(gen)])};
auto index_distribution = std::uniform_int_distribution<std::size_t>{0, free_states_.size() - 1};
return Sophus::SE2d{Sophus::SO2d::sampleUniform(gen), free_states_[index_distribution(gen)]};
}

/// Gets the importance weight for a particle with the provided state.
Expand Down Expand Up @@ -166,22 +171,11 @@ class BeamSensorModel : public Mixin {
}

private:
static std::vector<std::size_t> make_free_cells_vector(const OccupancyGrid& grid) {
auto free_cells = std::vector<std::size_t>{};
free_cells.reserve(grid.size());
for (std::size_t index = 0; index < grid.size(); ++index) {
if (OccupancyGrid::Traits::is_free(grid.data()[index])) {
free_cells.push_back(index);
}
}
return free_cells;
}

param_type params_;
OccupancyGrid grid_;
std::vector<Eigen::Vector2d> free_states_;
std::vector<std::pair<double, double>> points_;
std::vector<std::size_t> free_cells_;
mutable std::shared_mutex points_mutex_;
param_type params_;
};

} // namespace beluga
Expand Down
167 changes: 167 additions & 0 deletions beluga/include/beluga/sensor/data/dense_grid.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright 2023 Ekumen, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef BELUGA_SENSOR_DATA_DENSE_GRID_HPP
#define BELUGA_SENSOR_DATA_DENSE_GRID_HPP

#include <optional>
#include <vector>

#include <beluga/sensor/data/regular_grid.hpp>

#include <Eigen/Core>

/**
* \file
* \brief Concepts and abstract implementations of dense grids.
*/

namespace beluga {

/**
* \page DenseGrid2Page Beluga named requirements: DenseGrid2
*
* Dense grids support random, indexed access to each cell. These
* grids have a finite extent and thus can hold cell data in finite
* memory. Dense grids are regular grids, meaning they satisfy
* \ref RegularGrid2Page requirements.
*
* A type `G` satisfies `DenseGrid2` requirements if it satisfies
* \ref RegularGrid2Page and given `g` a possibly const instance of `G`:
* - `g.width()` returns the grid width, in grid cells along the grid
* x-axis, as an `std::size_t` value.
* - `g.height()` returns the grid height, in grid cells along the grid
* y-axis, as an `std::size_t` value.
* - given possibly const grid cell coordinates `xi` and `yi` of type `int`,
* `g.contains(xi, yi)` checks whether such cell is included in the grid;
* - given possibly const grid cell coordinates `pi` of `Eigen::Vector2i` type,
* `g.contains(p)` checks whether such cell is included in the grid;
* - given possibly const grid cell coordinates `xi` and `yi` of type `int`,
* `g.index_at(xi, yi)` retrieves the corresponding cell index of some type;
* - given possibly const grid cell coordinates `pi` of `Eigen::Vector2i` type,
* `g.index_at(pi)` retrieves the corresponding cell index of some type;
* - given possibly const grid cell coordinates `xi` and `yi` of type `int`,
* `g.data_at(xi, yi)` optionally returns cell data, if cell is included;
* - given possibly const grid cell coordinates `pi` of `Eigen::Vector2i` type,
* `g.data_at(p)` optionally returns cell data, if cell is included;
* - given possibly const grid cell index `i` of some type, `g.data_at(i)`
* optionally returns cell data, if cell is included;
* - given possibly const embedding space coordinates `x` and `y` of type `double`,
* `g.data_near(x, y)` optionally returns cell data, if cell is included;
* - given possibly const embedding space coordinates `p` of `Eigen::Vector2d` type,
* `g.data_near(p)` optionally returns cell data, if cell is included;
* - given possibly const grid cell coordinates `xi` and `yi` of type `int`,
* `g.neighborhood4(xi, yi)` computes the cell 4-connected neighborhood as
* a range of `Eigen::Vector2i` type;
* - given possibly const grid cell coordinates `pi` of `Eigen::Vector2i` type,
* `g.neighborhood4(p)` computes the cell 4-connected neighborhood as a
* range of `Eigen::Vector2i` type.
*/

/// Dense 2D grid base type.
/**
* When instantiated, it satisfies \ref DenseGrid2Page.
*
* \tparam Derived Concrete dense grid type. It must define
* `Derived::width()`, `Derived::height()`, `Derived::resolution()`,
* `Derived::data_at(index)`, and `Derived::index_at(int, int)`
* as described in \ref DenseGrid2Page.
*/
template <typename Derived>
class BaseDenseGrid2 : public BaseRegularGrid2<Derived> {
public:
/// Checks if a cell is included in the grid.
/**
* \param xi Grid cell x-axis coordinate.
* \param yi Grid cell y-axis coordinate.
*/
[[nodiscard]] bool contains(int xi, int yi) const {
return xi >= 0 && xi < static_cast<int>(this->self().width()) && yi >= 0 &&
yi < static_cast<int>(this->self().height());
}

/// Checks if a cell is included in the grid.
/**
* \param pi Grid cell coordinates.
*/
[[nodiscard]] bool contains(const Eigen::Vector2i& pi) const { return this->self().contains(pi.x(), pi.y()); }

/// Gets cell data, if included.
/**
* \param xi Grid cell x-axis coordinate.
* \param yi Grid cell y-axis coordinate.
* \return Cell data if included, `std::nullopt` otherwise.
*/
[[nodiscard]] auto data_at(int xi, int yi) const {
return this->self().contains(xi, yi) ? this->self().data_at(this->self().index_at(xi, yi)) : std::nullopt;
}

/// Gets cell data, if included.
/**
* \param pi Grid cell coordinates.
* \return Cell data if included, `std::nullopt` otherwise.
*/
[[nodiscard]] auto data_at(const Eigen::Vector2i& pi) const { return this->self().data_at(pi.x(), pi.y()); }

/// Gets nearest cell data, if included.
/**
* \param x Plane x-axis coordinate.
* \param y Plane y-axis coordinate.
* \return Cell data if included, `std::nullopt` otherwise.
*/
[[nodiscard]] auto data_near(double x, double y) const { return this->self().data_at(this->self().cell_near(x, y)); }

/// Gets nearest cell data, if included.
/**
* \param p Plane coordinates.
* \return Cell data if included, `std::nullopt` otherwise.
*/
[[nodiscard]] auto data_near(const Eigen::Vector2d& p) const { return this->self().data_near(p.x(), p.y()); }

/// Computes 4-connected neighborhood for cell.
/**
* \param xi Grid cell x-axis coordinate.
* \param yi Grid cell y-axis coordinate.
* \return range of neighbor cells.
*/
[[nodiscard]] auto neighborhood4(int xi, int yi) const {
auto result = std::vector<Eigen::Vector2i>{};
if (xi < static_cast<int>(this->self().width() - 1)) {
result.emplace_back(xi + 1, yi);
}
if (yi < static_cast<int>(this->self().height() - 1)) {
result.emplace_back(xi, yi + 1);
}
if (xi > 0) {
result.emplace_back(xi - 1, yi);
}
if (yi > 0) {
result.emplace_back(xi, yi - 1);
}
return result;
}

/// Computes 4-connected neighborhood for cell.
/**
* \param pi Grid cell coordinates.
* \return range of neighbor cells.
*/
[[nodiscard]] auto neighborhood4(const Eigen::Vector2i& pi) const {
return this->self().neighborhood4(pi.x(), pi.y());
}
};

} // namespace beluga

#endif
Loading

0 comments on commit 640c23d

Please sign in to comment.