Skip to content

File angle_corrector_correction_based.hpp

File List > decoders > angle_corrector_correction_based.hpp

Go to the documentation of this file

// Copyright 2024 TIER IV, 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.

#pragma once

#include "nebula_core_decoders/angles.hpp"
#include "nebula_hesai_common/hesai_common.hpp"
#include "nebula_hesai_decoders/decoders/angle_corrector.hpp"

#include <nebula_core_common/nebula_common.hpp>

#include <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <string>

namespace nebula::drivers
{

template <size_t ChannelN, size_t AngleUnit>
class AngleCorrectorCorrectionBased : public AngleCorrector<HesaiCorrection, ChannelN>
{
private:
  static constexpr size_t max_azimuth = 360 * AngleUnit;
  std::shared_ptr<const HesaiCorrection> correction_;
  rclcpp::Logger logger_;

  std::array<float, max_azimuth> cos_{};
  std::array<float, max_azimuth> sin_{};

  [[nodiscard]] size_t find_field(int32_t azimuth) const
  {
    for (size_t i = 0; i < correction_->frameNumber; ++i) {
      int32_t start_azimuth = correction_->startFrame[i];
      int32_t end_azimuth = correction_->endFrame[i];
      if (angle_is_between(start_azimuth, end_azimuth, azimuth)) return i;
    }

    std::stringstream ss;
    ss << "Azimuth " << azimuth << " not in any field\n";
    for (size_t i = 0; i < correction_->frameNumber; ++i) {
      ss << "Field " << i << ": " << correction_->startFrame[i] << " - " << correction_->endFrame[i]
         << '\n';
    }
    throw std::runtime_error(ss.str());
  }

  [[nodiscard]] int32_t to_exact_angle(double angle_deg) const
  {
    return std::round(angle_deg * AngleUnit);
  }

  [[nodiscard]] float to_radians(int32_t angle_exact) const
  {
    return deg2rad(angle_exact / static_cast<double>(AngleUnit));
  }

  [[nodiscard]] int32_t get_corrected_azimuth(
    size_t field, uint32_t block_azimuth, size_t channel_id) const
  {
    assert(field < correction_->frameNumber);
    assert(channel_id < ChannelN);

    int32_t azimuth_exact = (block_azimuth - correction_->startFrame[field]) * 2;
    azimuth_exact -= correction_->azimuth[channel_id];
    azimuth_exact += correction_->get_azimuth_adjust_v3(channel_id, block_azimuth) *
                     static_cast<int32_t>(AngleUnit / 100);
    azimuth_exact = normalize_angle(azimuth_exact, max_azimuth);

    assert(azimuth_exact >= 10 * static_cast<int32_t>(AngleUnit));
    assert(azimuth_exact <= 170 * static_cast<int32_t>(AngleUnit));
    return azimuth_exact;
  }

  [[nodiscard]] int32_t get_corrected_elevation(uint32_t block_azimuth, size_t channel_id) const
  {
    assert(channel_id < ChannelN);

    int32_t elevation_exact = correction_->elevation[channel_id];
    elevation_exact += correction_->get_elevation_adjust_v3(channel_id, block_azimuth) *
                       static_cast<int32_t>(AngleUnit / 100);
    return elevation_exact;
  }

public:
  explicit AngleCorrectorCorrectionBased(
    const std::shared_ptr<const HesaiCorrection> & sensor_correction)
  : correction_(sensor_correction), logger_(rclcpp::get_logger("AngleCorrectorCorrectionBased"))
  {
    if (sensor_correction == nullptr) {
      throw std::runtime_error(
        "Cannot instantiate AngleCorrectorCorrectionBased without correction data");
    }

    logger_.set_level(rclcpp::Logger::Level::Debug);

    // ////////////////////////////////////////
    // Trigonometry lookup tables
    // ////////////////////////////////////////

    for (size_t i = 0; i < max_azimuth; ++i) {
      float rad = 2.f * i * M_PIf / max_azimuth;
      cos_[i] = cosf(rad);
      sin_[i] = sinf(rad);
    }
  }

  [[nodiscard]] CorrectedAngleData get_corrected_angle_data(
    uint32_t block_azimuth, uint32_t channel_id) const override
  {
    int32_t elevation_exact = get_corrected_elevation(block_azimuth, channel_id);

    // Allow negative angles in the radian value. This makes visualization of this field nicer and
    // should have no other mathematical implications in downstream modules.
    float elevation_rad = 2.f * elevation_exact * M_PI / max_azimuth;
    // Then, normalize the integer value to the positive [0, MAX_AZIMUTH] range for array indexing
    elevation_exact = normalize_angle(elevation_exact, max_azimuth);

    size_t field = find_field(block_azimuth);
    int32_t azimuth_exact = get_corrected_azimuth(field, block_azimuth, channel_id);
    float azimuth_rad = to_radians(azimuth_exact);

    return {azimuth_rad,         elevation_rad,         sin_[azimuth_exact],
            cos_[azimuth_exact], sin_[elevation_exact], cos_[elevation_exact]};
  }

  [[nodiscard]] CorrectedAzimuths<ChannelN, float> get_corrected_azimuths(
    uint32_t block_azimuth) const override
  {
    CorrectedAzimuths<ChannelN, float> corrected_azimuths;
    size_t field = find_field(block_azimuth);

    for (size_t channel_id = 0; channel_id < ChannelN; ++channel_id) {
      int32_t azimuth_exact = get_corrected_azimuth(field, block_azimuth, channel_id);
      corrected_azimuths.azimuths[channel_id] = to_radians(azimuth_exact);
    }

    const auto & az = corrected_azimuths.azimuths;
    corrected_azimuths.min_correction_index = std::min_element(az.begin(), az.end()) - az.begin();
    corrected_azimuths.max_correction_index = std::max_element(az.begin(), az.end()) - az.begin();

    return corrected_azimuths;
  }
};

}  // namespace nebula::drivers