Skip to content

File angle_corrector_calibration_based.hpp

File List > decoders > angle_corrector_calibration_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 <cmath>
#include <cstdint>
#include <memory>

namespace nebula::drivers
{

template <size_t ChannelN, size_t AngleUnit>
class AngleCorrectorCalibrationBased
: public AngleCorrector<HesaiCalibrationConfiguration, ChannelN>
{
private:
  static constexpr size_t max_azimuth = 360 * AngleUnit;

  std::array<float, ChannelN> elevation_angle_rad_{};
  std::array<float, ChannelN> azimuth_offset_rad_{};
  std::array<float, max_azimuth> block_azimuth_rad_{};

  std::array<float, ChannelN> elevation_cos_{};
  std::array<float, ChannelN> elevation_sin_{};
  std::array<std::array<float, ChannelN>, max_azimuth> azimuth_cos_{};
  std::array<std::array<float, ChannelN>, max_azimuth> azimuth_sin_{};

  size_t min_correction_index_{};
  size_t max_correction_index_{};

  [[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));
  }

public:
  explicit AngleCorrectorCalibrationBased(
    const std::shared_ptr<const HesaiCalibrationConfiguration> & sensor_calibration)
  {
    if (sensor_calibration == nullptr) {
      throw std::runtime_error(
        "Cannot instantiate AngleCorrectorCalibrationBased without calibration data");
    }

    // ////////////////////////////////////////
    // Elevation lookup tables
    // ////////////////////////////////////////

    for (size_t channel_id = 0; channel_id < ChannelN; ++channel_id) {
      float elevation_angle_deg = sensor_calibration->elev_angle_map.at(channel_id);
      float azimuth_offset_deg = sensor_calibration->azimuth_offset_map.at(channel_id);

      elevation_angle_rad_[channel_id] = deg2rad(elevation_angle_deg);
      azimuth_offset_rad_[channel_id] = deg2rad(azimuth_offset_deg);

      elevation_cos_[channel_id] = cosf(elevation_angle_rad_[channel_id]);
      elevation_sin_[channel_id] = sinf(elevation_angle_rad_[channel_id]);
    }

    // ////////////////////////////////////////
    // Azimuth lookup tables
    // ////////////////////////////////////////

    for (size_t block_azimuth = 0; block_azimuth < max_azimuth; block_azimuth++) {
      block_azimuth_rad_[block_azimuth] = to_radians(block_azimuth);
      for (size_t channel_id = 0; channel_id < ChannelN; ++channel_id) {
        float spatial_azimuth_rad =
          block_azimuth_rad_[block_azimuth] + azimuth_offset_rad_[channel_id];
        azimuth_cos_[block_azimuth][channel_id] = cosf(spatial_azimuth_rad);
        azimuth_sin_[block_azimuth][channel_id] = sinf(spatial_azimuth_rad);
      }
    }

    const auto & az = azimuth_offset_rad_;
    min_correction_index_ = std::min_element(az.begin(), az.end()) - az.begin();
    max_correction_index_ = std::max_element(az.begin(), az.end()) - az.begin();
  }

  [[nodiscard]] CorrectedAngleData get_corrected_angle_data(
    uint32_t block_azimuth, uint32_t channel_id) const override
  {
    float azimuth_rad = block_azimuth_rad_[block_azimuth] + azimuth_offset_rad_[channel_id];
    azimuth_rad = normalize_angle(azimuth_rad, M_PIf * 2);

    float elevation_rad = elevation_angle_rad_[channel_id];

    return {
      azimuth_rad,
      elevation_rad,
      azimuth_sin_[block_azimuth][channel_id],
      azimuth_cos_[block_azimuth][channel_id],
      elevation_sin_[channel_id],
      elevation_cos_[channel_id]};
  }

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

    for (size_t channel_id = 0; channel_id < ChannelN; ++channel_id) {
      float exact_azimuth = block_azimuth_rad + azimuth_offset_rad_[channel_id];
      exact_azimuth = normalize_angle(exact_azimuth, 2 * M_PIf);
      corrected_azimuths.azimuths[channel_id] = exact_azimuth;
    }

    // Use precomputed min/max indices based on correction terms (offsets).
    // min_correction_index = channel that lags behind (smallest offset)
    // max_correction_index = channel that races ahead (largest offset)
    // These are invariant across block_azimuth values.
    corrected_azimuths.min_correction_index = min_correction_index_;
    corrected_azimuths.max_correction_index = max_correction_index_;

    return corrected_azimuths;
  }
};

}  // namespace nebula::drivers