Program Listing for File pose_util.h

Return to documentation for file (/var/lib/jenkins/workspace/SDK/public-release-ouster-sdk/ouster-sdk/ouster_client/include/ouster/pose_util.h)

#pragma once

#include <Eigen/Dense>
#include <cmath>
#include <cstddef>
#include <limits>
#include <nonstd/optional.hpp>
#include <stdexcept>
#include <type_traits>
#include <vector>

#include "ouster/cartesian.h"
#include "ouster/impl/transform_homogeneous.h"
#include "ouster/impl/transform_vector.h"
#include "ouster/lidar_scan.h"
#include "ouster/lidar_scan_set.h"
#include "ouster/visibility.h"
#include "ouster/xyzlut.h"

namespace ouster {
namespace sdk {
namespace core {

using Poses = MatrixX16dR;
using Pose = Vector16d;

template <typename T>
using PointsT [[deprecated("Use PointCloudXYZ instead")]] = PointCloudXYZ<T>;
template <typename T>
using PosesT = MatrixX16R<T>;

template <typename T>
void dewarp(Eigen::Ref<PointCloudXYZ<T>> dewarped,
            const Eigen::Ref<const PointCloudXYZ<T>> points,
            const Eigen::Ref<const PosesT<T>> poses) {
    const int W = poses.rows();                  // Number of pose matrices
    const int H = points.rows() / poses.rows();  // Points per pose matrix

#ifdef __OUSTER_UTILIZE_OPENMP__
#pragma omp parallel for schedule(static)
#endif
    for (int w = 0; w < W; ++w) {
        // Map the w-th row of `poses` (flattened 4×4) back into a 4×4 matrix
        Eigen::Map<const Matrix4R<T>> pose_matrix(poses.row(w).data());
        const Matrix3R<T> rotation = pose_matrix.template topLeftCorner<3, 3>();
        const Eigen::Vector3<T> translation =
            pose_matrix.template topRightCorner<3, 1>();

        // For each point corresponding to this pose
        for (int i = 0; i < H; ++i) {
            const Eigen::Index ix = i * W + w;
            const Eigen::Vector3<T> s = points.row(ix);
            dewarped.row(ix) = rotation * s + translation;
        }
    }
}

template <typename T>
PointCloudXYZ<T> dewarp(const Eigen::Ref<const PointCloudXYZ<T>>& points,
                        const Eigen::Ref<const PosesT<T>> poses) {
    // Allocate output with the same shape as input
    PointCloudXYZ<T> dewarped(points.rows(), points.cols());

    // Call the in‐place overload
    dewarp<T>(dewarped, points, poses);

    return dewarped;
}

OUSTER_API_FUNCTION
inline PointCloudXYZd dewarp(const PointCloudXYZd& points, const Pose& pose) {
    // forward to your templated two-arg version
    PosesT<double> poses(1, pose.size());
    poses.row(0) = pose;
    return dewarp<double>(points, poses);
}

template <typename T>
void transform(Eigen::Ref<PointCloudXYZ<T>> transformed,
               const Eigen::Ref<const PointCloudXYZ<T>> points,
               const Eigen::Ref<const Vector16<T>> pose) {
    Matrix4R<T> pose_matrix = Eigen::Map<const Matrix4R<T>>(pose.data());
    auto rotation = pose_matrix.template topLeftCorner<3, 3>();
    auto translation = pose_matrix.template topRightCorner<3, 1>();

    for (Eigen::Index i = 0; i < points.rows(); ++i) {
        const Eigen::Vector3<T> p = points.row(i);
        const Eigen::Vector3<T> new_p = (rotation * p) + translation;
        transformed.row(i) = new_p.transpose();
    }
}

template <typename T>
PointCloudXYZ<T> transform(const Eigen::Ref<const PointCloudXYZ<T>> points,
                           const Eigen::Ref<const Vector16<T>> pose) {
    PointCloudXYZ<T> transformed(points.rows(), points.cols());
    transform<T>(transformed, points, pose);
    return transformed;
}

OUSTER_API_FUNCTION
inline PointCloudXYZd transform(const PointCloudXYZd& points,
                                const Pose& pose) {
    // forward to your templated two-arg version
    return transform<double>(points, pose);
}

// NOTE[UN]: there is no point in templatizing the matrices here since the lie
// group operations are only defined for double precision in
// impl/transform_homogeneous.h
template <typename T>
std::vector<Matrix4dR> interp_pose(const std::vector<T>& x_interp, T t0,
                                   Eigen::Ref<const Matrix4dR> x0, T t1,
                                   Eigen::Ref<const Matrix4dR> x1) {
    static_assert(std::is_signed<T>::value, "T must be a signed type");
    T duration = t1 - t0;
    if (std::abs(duration) < std::numeric_limits<T>::epsilon()) {
        throw std::invalid_argument(
            "Cannot interpolate with zero duration between poses");
    }
    std::vector<Matrix4dR> result;
    result.resize(x_interp.size());
    ouster::sdk::core::impl::PoseH a(x0);
    ouster::sdk::core::impl::PoseH b(x1);
    // The relative transformation from a to b is (a^-1 * b)
    ouster::sdk::core::impl::PoseH a_inv = a.inverse();
    ouster::sdk::core::impl::PoseV twist = (a_inv * b).log();
    ouster::sdk::core::impl::PoseV delta;
    ouster::sdk::core::impl::PoseH interp;
    for (size_t idx = 0; idx < x_interp.size(); ++idx) {
        auto t = (x_interp[idx] - t0) / duration;
        delta = t * twist;
        interp = a * delta.exp();
        result[idx] = interp.matrix();
    }
    return result;
}
template <typename T, typename Scalar>
OUSTER_API_FUNCTION PosesT<Scalar> interp_pose(
    const Eigen::Ref<const Eigen::VectorX<T>> x_interp,
    const Eigen::Ref<const Eigen::VectorX<T>> x_known,
    const Eigen::Ref<const PosesT<Scalar>> poses_known) {
    if (x_known.size() != poses_known.rows()) {
        throw std::invalid_argument(
            "x_known and poses_known sizes are not matching");
    }
    if (x_known.size() < 2) {
        throw std::invalid_argument(
            "Not enough evaluation poses for interpolation");
    }

    const size_t x_known_n = x_known.size();
    const size_t x_interp_n = x_interp.size();
    size_t current_interval = 0;

    PosesT<Scalar> results(x_interp_n, 16);
    std::vector<ouster::sdk::core::impl::PoseV> pose_diff_intervals(x_known_n -
                                                                    1);

    for (size_t i = 0; i < x_known_n - 1; i++) {
        T x = x_known(i);
        if (i > 0 && x <= x_known(i - 1)) {
            throw std::invalid_argument(
                "input x_known values are not monotonically increasing or "
                "values repeated");
        }
        // Map the i-th row of poses_known (flattened 4×4) back into a 4×4
        // matrix
        Eigen::Map<const Matrix4R<Scalar>> prev_pose_matrix(
            poses_known.row(i).data());
        Eigen::Map<const Matrix4R<Scalar>> curr_pose_matrix(
            poses_known.row(i + 1).data());

        ouster::sdk::core::impl::PoseH prev_poseh(
            prev_pose_matrix.template cast<double>());
        ouster::sdk::core::impl::PoseH curr_poseh(
            curr_pose_matrix.template cast<double>());
        ouster::sdk::core::impl::PoseV pose_diff =
            (curr_poseh * ouster::sdk::core::impl::PoseH(prev_poseh.inverse()))
                .log();
        pose_diff_intervals[i] = pose_diff;
    }

    nonstd::optional<T> last_x = nonstd::nullopt;
    for (size_t i = 0; i < x_interp_n; i++) {
        T x = x_interp(i);
        if (x <= x_known(0)) {
            T x0 = x_known(0);
            T x1 = x_known(1);
            double ratio = -1 * double(x0 - x) / double(x1 - x0);

            Eigen::Map<const Matrix4R<Scalar>> first_pose_matrix(
                poses_known.row(0).data());
            ouster::sdk::core::impl::PoseH prev_poseh(
                first_pose_matrix.template cast<double>());
            ouster::sdk::core::impl::PoseV pose_diff = pose_diff_intervals[0];
            ouster::sdk::core::impl::PoseV delta = ratio * pose_diff;
            ouster::sdk::core::impl::PoseH interp_poseh =
                (delta.exp()) * prev_poseh;
            Matrix4R<Scalar> result_matrix =
                interp_poseh.matrix().template cast<Scalar>();
            Eigen::Map<const Vector16<Scalar>> result_row(result_matrix.data());
            results.row(i) = result_row;
            continue;
        }

        if (x >= x_known(x_known_n - 1)) {
            T x0 = x_known(x_known_n - 2);
            T x1 = x_known(x_known_n - 1);
            double ratio = double(x - x1) / double(x1 - x0);

            Eigen::Map<const Matrix4R<Scalar>> last_pose_matrix(
                poses_known.row(x_known_n - 1).data());
            ouster::sdk::core::impl::PoseH prev_poseh(
                last_pose_matrix.template cast<double>());
            ouster::sdk::core::impl::PoseV pose_diff =
                pose_diff_intervals.back();
            ouster::sdk::core::impl::PoseV delta = ratio * pose_diff;
            ouster::sdk::core::impl::PoseH interp_poseh =
                (delta.exp()) * prev_poseh;
            Matrix4R<Scalar> result_matrix =
                interp_poseh.matrix().template cast<Scalar>();
            Eigen::Map<const Vector16<Scalar>> result_row(result_matrix.data());
            results.row(i) = result_row;
            continue;
        }

        if (last_x != nonstd::nullopt && x < last_x) {
            while (current_interval > 0 && x < x_known(current_interval)) {
                current_interval--;
            }
        }
        last_x = x;

        while (current_interval < x_known_n - 1 &&
               x_known(current_interval + 1) < x) {
            current_interval++;
        }

        T x0 = x_known(current_interval);
        T x1 = x_known(current_interval + 1);
        double ratio = double(x - x0) / double(x1 - x0);

        Eigen::Map<const Eigen::Matrix<Scalar, 4, 4, Eigen::RowMajor>>
            interval_pose_matrix(poses_known.row(current_interval).data());
        ouster::sdk::core::impl::PoseH prev_poseh(
            interval_pose_matrix.template cast<double>());
        ouster::sdk::core::impl::PoseV pose_diff =
            pose_diff_intervals[current_interval];
        ouster::sdk::core::impl::PoseV delta = ratio * pose_diff;
        ouster::sdk::core::impl::PoseH interp_poseh = delta.exp() * prev_poseh;
        Eigen::Matrix<Scalar, 4, 4, Eigen::RowMajor> result_matrix =
            interp_poseh.matrix().template cast<Scalar>();
        Eigen::Map<const Eigen::Matrix<Scalar, 1, 16, Eigen::RowMajor>>
            result_row(result_matrix.data());
        results.row(i) = result_row;
    }

    return results;
}

OUSTER_API_FUNCTION
Poses interp_pose(const std::vector<double>& x_interp,
                  const std::vector<double>& x_known, const Poses& poses_known);

OUSTER_API_FUNCTION
Poses interp_pose(const std::vector<double>& x_interp,
                  const std::vector<double>& x_known,
                  const std::vector<Matrix4dR>& poses_known);

template <typename T>
OUSTER_API_FUNCTION std::vector<Eigen::Vector3<T>> dewarp(
    const LidarScan& lidar_scan, const XYZLutT<T>& xyzlut, double min_range,
    double max_range) {
    // Filter out points with zero range and collect points/timestamps
    auto range = lidar_scan.field<uint32_t>(ChanField::RANGE);
    // Note[UN]: for future we can optimize this method even further by
    //           embedding the cartesian directly into this and allowing
    //           it to skip over invalid columns.
    PointCloudXYZ<T> pts = cartesianT(range, xyzlut.direction, xyzlut.offset);

    // convert min/max range limits to millimeters
    uint32_t min_r = static_cast<uint32_t>(std::ceil(min_range * 1e3));
    uint32_t max_r = static_cast<uint32_t>(std::floor(max_range * 1e3));

    // Only dewarp valid points that are within range
    const int height = range.rows();
    const int width = range.cols();
    // Pre-allocate memory that can fit all points that are valid
    const int start_col = lidar_scan.get_first_valid_column();
    const int stop_col = lidar_scan.get_last_valid_column();
    if (start_col < 0 || stop_col < 0 || stop_col < start_col) {
        return {};
    }
    std::vector<Eigen::Vector3<T>> dewarped_pts;
    dewarped_pts.reserve(height * (stop_col - start_col + 1));

    auto poses = Eigen::Map<const MatrixX16dR>(lidar_scan.pose().get<double>(),
                                               lidar_scan.w, 16);
    Eigen::Ref<const LidarScan::Header<uint32_t>> status = lidar_scan.status();

    for (int x = start_col; x <= stop_col; ++x) {
        if (status[x] == 0) {
            continue;
        }
        Eigen::Map<const Matrix4dR> pose(poses.row(x).data());
        const Matrix3R<T> rotation = pose.topLeftCorner<3, 3>().cast<T>();
        const Eigen::Vector3<T> translation =
            pose.topRightCorner<3, 1>().cast<T>();
        for (int y = 0; y < height; ++y) {
            uint32_t r = range(y, x);
            if (r >= min_r && r <= max_r) {
                Eigen::Vector3<T> pt = pts.row(y * width + x);
                pt = rotation * pt + translation;
                dewarped_pts.emplace_back(std::move(pt));
            }
        }
    }

    return dewarped_pts;
}

namespace impl {

// A method that provides a quick upper bound on the number of points that can
// be produced from a LidarScanSet
size_t max_number_of_valid_points(const LidarScanSet& lidar_scan_set);

}  // namespace impl

template <typename T>
OUSTER_API_FUNCTION std::vector<Eigen::Vector3<T>> dewarp(
    const LidarScanSet& lidar_scan_set, const std::vector<XYZLutT<T>>& xyzluts,
    double min_range, double max_range) {
    assert(lidar_scan_set.size() == xyzluts.size() &&
           "Number of scans and number of XYZLuts must be the same");
    std::vector<Eigen::Vector3<T>> total_pts;
    total_pts.reserve(impl::max_number_of_valid_points(lidar_scan_set));

    for (size_t idx : lidar_scan_set.valid_indices()) {
        const auto& scan = *lidar_scan_set[idx];
        auto dewarped_pts = dewarp(scan, xyzluts[idx], min_range, max_range);
        total_pts.insert(total_pts.end(),
                         std::make_move_iterator(dewarped_pts.begin()),
                         std::make_move_iterator(dewarped_pts.end()));
    }

    return total_pts;
}

}  // namespace core
}  // namespace sdk
}  // namespace ouster