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>
#include <optional>
#include <ostream>
#include <utility>

namespace nebula::drivers
{

template <size_t ChannelN, size_t AngleUnit>
class AngleCorrectorCalibrationBased : public AngleCorrector<HesaiCalibrationConfiguration>
{
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_{};

public:
  uint32_t emit_angle_raw_;
  uint32_t timestamp_reset_angle_raw_;
  uint32_t fov_start_raw_;
  uint32_t fov_end_raw_;

  bool is_360_;

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

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

    int32_t correction_min = INT32_MAX;
    int32_t correction_max = INT32_MIN;

    auto round_away_from_zero = [](float value) {
      return (value < 0) ? std::floor(value) : std::ceil(value);
    };

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

      int32_t azimuth_offset_raw = round_away_from_zero(azimuth_offset_deg * AngleUnit);
      correction_min = std::min(correction_min, azimuth_offset_raw);
      correction_max = std::max(correction_max, azimuth_offset_raw);

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

    // ////////////////////////////////////////
    // Raw azimuth threshold angles
    // ////////////////////////////////////////

    int32_t emit_angle_raw = std::ceil(scan_cut_azimuth_deg * AngleUnit);
    emit_angle_raw -= correction_min;
    emit_angle_raw_ = normalize_angle<int32_t>(emit_angle_raw, max_azimuth);

    int32_t fov_start_raw = std::floor(fov_start_azimuth_deg * AngleUnit);
    fov_start_raw -= correction_max;
    fov_start_raw_ = normalize_angle<int32_t>(fov_start_raw, max_azimuth);

    int32_t fov_end_raw = std::ceil(fov_end_azimuth_deg * AngleUnit);
    fov_end_raw -= correction_min;
    fov_end_raw_ = normalize_angle<int32_t>(fov_end_raw, max_azimuth);

    // Reset timestamp on FoV start if FoV < 360 deg and scan is cut at FoV end.
    // Otherwise, reset timestamp on publish
    is_360_ =
      normalize_angle(fov_start_azimuth_deg, 360.) == normalize_angle(fov_end_azimuth_deg, 360.);
    bool reset_timestamp_on_publish = is_360_ || (normalize_angle(fov_end_azimuth_deg, 360.) !=
                                                  normalize_angle(scan_cut_azimuth_deg, 360.));

    if (reset_timestamp_on_publish) {
      int32_t timestamp_reset_angle_raw = std::floor(scan_cut_azimuth_deg * AngleUnit);
      timestamp_reset_angle_raw -= correction_max;
      timestamp_reset_angle_raw_ = normalize_angle<int32_t>(timestamp_reset_angle_raw, max_azimuth);
    } else {
      timestamp_reset_angle_raw_ = fov_start_raw_;
    }

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

    for (size_t block_azimuth = 0; block_azimuth < max_azimuth; block_azimuth++) {
      block_azimuth_rad_[block_azimuth] = deg2rad(block_azimuth / static_cast<double>(AngleUnit));

      for (size_t channel_id = 0; channel_id < ChannelN; ++channel_id) {
        float precision_azimuth =
          block_azimuth_rad_[block_azimuth] + azimuth_offset_rad_[channel_id];

        azimuth_cos_[block_azimuth][channel_id] = cosf(precision_azimuth);
        azimuth_sin_[block_azimuth][channel_id] = sinf(precision_azimuth);
      }
    }
  }

  CorrectedAngleData get_corrected_angle_data(uint32_t block_azimuth, uint32_t channel_id) 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]};
  }

  bool passed_emit_angle(uint32_t last_azimuth, uint32_t current_azimuth) override
  {
    return angle_is_between(last_azimuth, current_azimuth, emit_angle_raw_, false);
  }

  bool passed_timestamp_reset_angle(uint32_t last_azimuth, uint32_t current_azimuth) override
  {
    return angle_is_between(last_azimuth, current_azimuth, timestamp_reset_angle_raw_, false);
  }

  bool is_inside_fov(uint32_t last_azimuth, uint32_t current_azimuth) override
  {
    if (is_360_) return true;
    return angle_is_between(fov_start_raw_, fov_end_raw_, current_azimuth) ||
           angle_is_between(timestamp_reset_angle_raw_, emit_angle_raw_, last_azimuth);
  }

  bool is_inside_overlap(uint32_t last_azimuth, uint32_t current_azimuth) override
  {
    return angle_is_between(timestamp_reset_angle_raw_, emit_angle_raw_, current_azimuth) ||
           angle_is_between(timestamp_reset_angle_raw_, emit_angle_raw_, last_azimuth);
  }
};

}  // namespace nebula::drivers