Program Listing for File bearing_sensor_model.hpp

Return to documentation for file (include/beluga/sensor/bearing_sensor_model.hpp)

// 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_BEARING_SENSOR_MODEL_HPP
#define BELUGA_SENSOR_BEARING_SENSOR_MODEL_HPP

// external
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/all.hpp>
#include <range/v3/view/transform.hpp>
#include <sophus/se2.hpp>
#include <sophus/se3.hpp>

// standard library
#include <cmath>
#include <random>
#include <utility>
#include <vector>

// project
#include <beluga/types/landmark_detection_types.hpp>

namespace beluga {

struct BearingModelParam {
  double sigma_bearing{1.0};
  Sophus::SE3d sensor_pose_in_robot;
};


template <class LandmarkMap, class StateType>
class BearingSensorModel {
 public:
  using state_type = StateType;
  using weight_type = double;
  using measurement_type = std::vector<LandmarkBearingDetection>;
  using map_type = LandmarkMap;
  using param_type = BearingModelParam;


  template <class... Args>
  explicit BearingSensorModel(param_type params, LandmarkMap landmark_map)
      : params_{std::move(params)}, landmark_map_{std::move(landmark_map)} {}


  [[nodiscard]] auto operator()(measurement_type&& detections) const {
    return [this, detections = std::move(detections)](const state_type& state) -> weight_type {
      Sophus::SE3d robot_pose_in_world;

      if constexpr (std::is_same_v<state_type, Sophus::SE3d>) {
        // The robot pose state is already given in 3D,
        robot_pose_in_world = state;
      } else {
        // The robot pose state is given in 2D. Notice that in this case
        // the 2D pose of the robot is assumed to be that of the robot footprint (projection of the robot
        // on the z=0 plane of the 3D world frame). This is so that we can tie the sensor reference frame
        // to the world frame where the landmarks are given without additional structural information.
        robot_pose_in_world = Sophus::SE3d{
            Sophus::SO3d::rotZ(state.so2().log()),
            Eigen::Vector3d{state.translation().x(), state.translation().y(), 0.0}};
      }

      // precalculate the sensor pose in the world frame
      const auto sensor_pose_in_world = robot_pose_in_world * params_.sensor_pose_in_robot;

      const auto detection_weight = [this, &sensor_pose_in_world](const auto& detection) {
        // find the landmark the most closely matches the sample bearing vector
        const auto opt_landmark_bearing_in_sensor = landmark_map_.find_closest_bearing_landmark(
            detection.detection_bearing_in_sensor, detection.category, sensor_pose_in_world);

        // if we did not find a matching landmark, return 0.0
        if (!opt_landmark_bearing_in_sensor) {
          return 0.0;
        }

        // recover sample bearing vector
        const auto detection_bearing_in_sensor = detection.detection_bearing_in_sensor.normalized();

        // recover landmark bearing vector
        const auto& landmark_bearing_in_sensor = *opt_landmark_bearing_in_sensor;

        // calculate the aperture angle between the detection and the landmark
        const auto cos_aperture = detection_bearing_in_sensor.dot(landmark_bearing_in_sensor);
        const auto sin_aperture = detection_bearing_in_sensor.cross(landmark_bearing_in_sensor).norm();

        // calculate the angle between the sample and the landmark
        const auto bearing_error = std::atan2(sin_aperture, cos_aperture);

        // model the probability of the landmark being detected as depending on the bearing error
        const auto bearing_error_prob =
            std::exp(-bearing_error * bearing_error / (2. * params_.sigma_bearing * params_.sigma_bearing));

        return bearing_error_prob;
      };

      return std::transform_reduce(detections.cbegin(), detections.cend(), 1.0, std::multiplies{}, detection_weight);
    };
  }

  void update_map(map_type&& map) { landmark_map_ = std::move(map); }

 private:
  param_type params_;
  LandmarkMap landmark_map_;
};

template <class LandmarkMap>
using BearingSensorModel2d = BearingSensorModel<LandmarkMap, Sophus::SE2d>;

template <class LandmarkMap>
using BearingSensorModel3d = BearingSensorModel<LandmarkMap, Sophus::SE3d>;

}  // namespace beluga

#endif