#![cfg_attr(not(feature = "std"), no_std)]
#![deny(rust_2018_idioms, unsafe_code, missing_docs)]
#![doc = include_str!("../README.md")]
#[cfg(not(feature = "std"))]
extern crate core as std;
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
use nalgebra::{
allocator::Allocator,
storage::{Owned, Storage},
DefaultAllocator, Dim, DimName, Isometry3, Matrix, Point3, RealField, SMatrix, Vector3, U1, U2,
U3,
};
#[cfg(feature = "std")]
pub mod intrinsic_test_utils;
mod intrinsics_perspective;
pub use intrinsics_perspective::{IntrinsicParametersPerspective, PerspectiveParams};
mod intrinsics_orthographic;
pub use intrinsics_orthographic::{IntrinsicParametersOrthographic, OrthographicParams};
mod extrinsics;
pub use extrinsics::ExtrinsicParameters;
mod camera;
pub use camera::Camera;
pub mod ray_bundle_types;
#[cfg(feature = "alloc")]
mod ray_intersection;
#[cfg(feature = "alloc")]
pub use ray_intersection::best_intersection_of_rays;
pub mod linearize;
#[cfg_attr(feature = "std", derive(Debug))]
#[non_exhaustive]
pub enum Error {
InvalidInput,
SvdFailed,
MinimumTwoRaysNeeded,
InvalidRotationMatrix,
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
#[cfg(feature = "std")]
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
#[derive(Clone)]
pub struct Pixels<R: RealField, NPTS: Dim, STORAGE> {
pub data: nalgebra::Matrix<R, NPTS, U2, STORAGE>,
}
impl<R: RealField, NPTS: Dim, STORAGE> Pixels<R, NPTS, STORAGE> {
#[inline]
pub fn new(data: nalgebra::Matrix<R, NPTS, U2, STORAGE>) -> Self {
Self { data }
}
}
pub trait CoordinateSystem {}
pub mod coordinate_system {
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub struct CameraFrame {}
impl crate::CoordinateSystem for CameraFrame {}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub struct WorldFrame {}
impl crate::CoordinateSystem for WorldFrame {}
}
pub use coordinate_system::{CameraFrame, WorldFrame};
pub struct Points<Coords: CoordinateSystem, R: RealField, NPTS: Dim, STORAGE> {
coords: std::marker::PhantomData<Coords>,
pub data: nalgebra::Matrix<R, NPTS, U3, STORAGE>,
}
#[cfg(feature = "std")]
impl<Coords: CoordinateSystem, R: RealField, NPTS: Dim, STORAGE: std::fmt::Debug> std::fmt::Debug
for Points<Coords, R, NPTS, STORAGE>
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Points")
.field("coords", &self.coords)
.field("data", &self.data)
.finish()
}
}
impl<Coords, R, NPTS, STORAGE> Points<Coords, R, NPTS, STORAGE>
where
Coords: CoordinateSystem,
R: RealField,
NPTS: Dim,
{
#[inline]
pub fn new(data: nalgebra::Matrix<R, NPTS, U3, STORAGE>) -> Self {
Self {
coords: std::marker::PhantomData,
data,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub struct RayBundle<Coords, BType, R, NPTS, StorageMultiple>
where
Coords: CoordinateSystem,
BType: Bundle<R>,
R: RealField,
NPTS: Dim,
StorageMultiple: Storage<R, NPTS, U3>,
{
coords: std::marker::PhantomData<Coords>,
pub data: Matrix<R, NPTS, U3, StorageMultiple>,
bundle_type: BType,
}
impl<Coords, BType, R, NPTS, StorageMultiple> RayBundle<Coords, BType, R, NPTS, StorageMultiple>
where
Coords: CoordinateSystem,
BType: Bundle<R>,
R: RealField,
NPTS: DimName,
StorageMultiple: Storage<R, NPTS, U3>,
DefaultAllocator: Allocator<NPTS, U3>,
{
#[inline]
pub fn directions(&self) -> Matrix<R, NPTS, U3, Owned<R, NPTS, U3>> {
self.bundle_type.directions(&self.data)
}
#[inline]
pub fn centers(&self) -> Matrix<R, NPTS, U3, Owned<R, NPTS, U3>> {
self.bundle_type.centers(&self.data)
}
}
impl<Coords, BType, R> RayBundle<Coords, BType, R, U1, Owned<R, U1, U3>>
where
Coords: CoordinateSystem,
BType: Bundle<R>,
R: RealField,
{
#[inline]
pub fn to_single_ray(&self) -> Ray<Coords, R> {
self.bundle_type.to_single_ray(&self.data)
}
}
impl<Coords, R, NPTS, StorageMultiple>
RayBundle<Coords, crate::ray_bundle_types::SharedOriginRayBundle<R>, R, NPTS, StorageMultiple>
where
Coords: CoordinateSystem,
R: RealField,
NPTS: Dim,
StorageMultiple: Storage<R, NPTS, U3>,
{
pub fn new_shared_zero_origin(data: Matrix<R, NPTS, U3, StorageMultiple>) -> Self {
let bundle_type = crate::ray_bundle_types::SharedOriginRayBundle::new_shared_zero_origin();
Self::new(bundle_type, data)
}
}
impl<Coords, R, NPTS, StorageMultiple>
RayBundle<
Coords,
crate::ray_bundle_types::SharedDirectionRayBundle<R>,
R,
NPTS,
StorageMultiple,
>
where
Coords: CoordinateSystem,
R: RealField,
NPTS: Dim,
StorageMultiple: Storage<R, NPTS, U3>,
{
pub fn new_shared_plusz_direction(data: Matrix<R, NPTS, U3, StorageMultiple>) -> Self {
let bundle_type =
crate::ray_bundle_types::SharedDirectionRayBundle::new_plusz_shared_direction();
Self::new(bundle_type, data)
}
}
impl<Coords, BType, R, NPTS, StorageMultiple> RayBundle<Coords, BType, R, NPTS, StorageMultiple>
where
Coords: CoordinateSystem,
BType: Bundle<R>,
R: RealField,
NPTS: Dim,
StorageMultiple: Storage<R, NPTS, U3>,
{
#[inline]
fn new(bundle_type: BType, data: nalgebra::Matrix<R, NPTS, U3, StorageMultiple>) -> Self {
Self {
coords: std::marker::PhantomData,
data,
bundle_type,
}
}
#[inline]
pub fn point_on_ray(&self) -> Points<Coords, R, NPTS, Owned<R, NPTS, U3>>
where
DefaultAllocator: Allocator<NPTS, U3>,
{
self.bundle_type.point_on_ray(&self.data)
}
#[inline]
pub fn point_on_ray_at_distance(
&self,
distance: R,
) -> Points<Coords, R, NPTS, Owned<R, NPTS, U3>>
where
DefaultAllocator: Allocator<NPTS, U3>,
{
self.bundle_type
.point_on_ray_at_distance(&self.data, distance)
}
#[inline]
fn to_pose<OutFrame>(
&self,
pose: Isometry3<R>,
) -> RayBundle<OutFrame, BType, R, NPTS, Owned<R, NPTS, U3>>
where
R: RealField,
NPTS: Dim,
OutFrame: CoordinateSystem,
DefaultAllocator: Allocator<NPTS, U3>,
{
self.bundle_type.to_pose(pose, &self.data)
}
}
pub struct Ray<Coords, R: RealField> {
pub center: SMatrix<R, 1, 3>,
pub direction: SMatrix<R, 1, 3>,
c: std::marker::PhantomData<Coords>,
}
impl<Coords, R: RealField> Ray<Coords, R> {
#[inline]
pub fn new(center: SMatrix<R, 1, 3>, direction: SMatrix<R, 1, 3>) -> Self {
Self {
center,
direction,
c: std::marker::PhantomData,
}
}
}
pub trait Bundle<R>
where
R: RealField,
{
fn to_single_ray<Coords>(&self, self_data: &SMatrix<R, 1, 3>) -> Ray<Coords, R>
where
Coords: CoordinateSystem;
fn directions<NPTS, StorageIn>(
&self,
self_data: &Matrix<R, NPTS, U3, StorageIn>,
) -> Matrix<R, NPTS, U3, Owned<R, NPTS, U3>>
where
NPTS: DimName,
StorageIn: Storage<R, NPTS, U3>,
DefaultAllocator: Allocator<NPTS, U3>;
fn centers<NPTS, StorageIn>(
&self,
self_data: &Matrix<R, NPTS, U3, StorageIn>,
) -> Matrix<R, NPTS, U3, Owned<R, NPTS, U3>>
where
NPTS: DimName,
StorageIn: Storage<R, NPTS, U3>,
DefaultAllocator: Allocator<NPTS, U3>;
fn point_on_ray<NPTS, StorageIn, OutFrame>(
&self,
self_data: &Matrix<R, NPTS, U3, StorageIn>,
) -> Points<OutFrame, R, NPTS, Owned<R, NPTS, U3>>
where
Self: Sized,
NPTS: Dim,
StorageIn: Storage<R, NPTS, U3>,
OutFrame: CoordinateSystem,
DefaultAllocator: Allocator<NPTS, U3>;
fn point_on_ray_at_distance<NPTS, StorageIn, OutFrame>(
&self,
self_data: &Matrix<R, NPTS, U3, StorageIn>,
distance: R,
) -> Points<OutFrame, R, NPTS, Owned<R, NPTS, U3>>
where
Self: Sized,
NPTS: Dim,
StorageIn: Storage<R, NPTS, U3>,
OutFrame: CoordinateSystem,
DefaultAllocator: Allocator<NPTS, U3>;
fn to_pose<NPTS, StorageIn, OutFrame>(
&self,
pose: Isometry3<R>,
self_data: &Matrix<R, NPTS, U3, StorageIn>,
) -> RayBundle<OutFrame, Self, R, NPTS, Owned<R, NPTS, U3>>
where
Self: Sized,
R: RealField,
NPTS: Dim,
StorageIn: Storage<R, NPTS, U3>,
OutFrame: CoordinateSystem,
DefaultAllocator: Allocator<NPTS, U3>;
}
pub trait IntrinsicParameters<R>: std::fmt::Debug + Clone
where
R: RealField,
{
type BundleType;
fn pixel_to_camera<IN, NPTS>(
&self,
pixels: &Pixels<R, NPTS, IN>,
) -> RayBundle<coordinate_system::CameraFrame, Self::BundleType, R, NPTS, Owned<R, NPTS, U3>>
where
Self::BundleType: Bundle<R>,
IN: Storage<R, NPTS, U2>,
NPTS: Dim,
DefaultAllocator: Allocator<U1, U2>, DefaultAllocator: Allocator<NPTS, U2>, DefaultAllocator: Allocator<NPTS, U3>;
fn camera_to_pixel<IN, NPTS>(
&self,
camera: &Points<coordinate_system::CameraFrame, R, NPTS, IN>,
) -> Pixels<R, NPTS, Owned<R, NPTS, U2>>
where
IN: Storage<R, NPTS, U3>,
NPTS: Dim,
DefaultAllocator: Allocator<NPTS, U2>;
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::convert;
#[cfg(not(feature = "std"))]
compile_error!("tests require std");
#[test]
fn rays_shared_origin() {
let b1 =
RayBundle::<WorldFrame, _, _, _, _>::new_shared_zero_origin(SMatrix::<_, 2, 3>::new(
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, ));
let actual_dist1 = b1.point_on_ray_at_distance(1.0).data;
{
let r1m = (1.0_f64 + 4.0 + 9.0).sqrt();
let r2m = (16.0_f64 + 25.0 + 36.0).sqrt();
let expected = SMatrix::<_, 2, 3>::new(
1.0 / r1m,
2.0 / r1m,
3.0 / r1m, 4.0 / r2m,
5.0 / r2m,
6.0 / r2m, );
approx::assert_abs_diff_eq!(actual_dist1, expected, epsilon = 1e-10);
}
let actual_dist10 = b1.point_on_ray_at_distance(10.0).data;
let actual = b1.point_on_ray().data;
for i in 0..actual_dist1.nrows() {
assert_on_line(actual_dist1.row(i), actual_dist10.row(i), actual.row(i));
}
}
#[test]
fn rays_shared_direction() {
let b1 = RayBundle::<WorldFrame, _, _, _, _>::new_shared_plusz_direction(
SMatrix::<_, 2, 3>::new(
1.0, 2.0, 0.0, 3.0, 4.0, 0.0, ),
);
let actual_dist10 = b1.point_on_ray_at_distance(10.0).data;
{
let expected_dist10 = SMatrix::<_, 2, 3>::new(
1.0, 2.0, 10.0, 3.0, 4.0, 10.0, );
approx::assert_abs_diff_eq!(actual_dist10, expected_dist10, epsilon = 1e-10);
}
let actual_dist0 = b1.point_on_ray_at_distance(0.0).data;
{
let expected_dist0 = SMatrix::<_, 2, 3>::new(
1.0, 2.0, 0.0, 3.0, 4.0, 0.0, );
approx::assert_abs_diff_eq!(actual_dist0, expected_dist0, epsilon = 1e-10);
}
let actual = b1.point_on_ray().data;
for i in 0..actual_dist0.nrows() {
assert_on_line(actual_dist0.row(i), actual_dist10.row(i), actual.row(i));
}
}
fn assert_on_line<R, S1, S2, S3>(
line_a: Matrix<R, U1, U3, S1>,
line_b: Matrix<R, U1, U3, S2>,
test_pt: Matrix<R, U1, U3, S3>,
) where
R: RealField,
S1: Storage<R, U1, U3>,
S2: Storage<R, U1, U3>,
S3: Storage<R, U1, U3>,
{
let dir = &line_b - &line_a;
let testx = &test_pt - &line_a;
let mag_dir = (dir[0].clone() * dir[0].clone()
+ dir[1].clone() * dir[1].clone()
+ dir[2].clone() * dir[2].clone())
.sqrt();
let mag_testx = (testx[0].clone() * testx[0].clone()
+ testx[1].clone() * testx[1].clone()
+ testx[2].clone() * testx[2].clone())
.sqrt();
let scale = mag_dir / mag_testx;
for j in 0..3 {
approx::assert_abs_diff_eq!(
testx[j].clone() * scale.clone(),
dir[j].clone(),
epsilon = convert(1e-10)
);
}
}
#[test]
#[cfg(feature = "serde-serialize")]
fn test_ray_bundle_serde() {
let expected =
RayBundle::<WorldFrame, _, _, _, _>::new_shared_plusz_direction(
SMatrix::<_, 2, 3>::new(
1.0, 2.0, 0.0, 3.0, 4.0, 0.0, ),
);
let buf = serde_json::to_string(&expected).unwrap();
let actual: RayBundle<_, _, _, _, _> = serde_json::from_str(&buf).unwrap();
assert!(expected == actual);
}
}