use crate::point::Point3f;
use crate::point_cloud::PointCloud;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrganizedPointCloud<T> {
pub width: usize,
pub height: usize,
pub points: Vec<Option<T>>,
pub is_dense: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct CameraIntrinsics {
pub fx: f32,
pub fy: f32,
pub cx: f32,
pub cy: f32,
}
impl CameraIntrinsics {
pub fn new(fx: f32, fy: f32, cx: f32, cy: f32) -> Self {
Self { fx, fy, cx, cy }
}
}
impl<T> OrganizedPointCloud<T> {
pub fn new(width: usize, height: usize) -> Self
where
T: Clone,
{
let mut points = Vec::with_capacity(width * height);
points.resize_with(width * height, || None);
Self { width, height, points, is_dense: false }
}
pub fn from_points(width: usize, height: usize, points: Vec<Option<T>>) -> Option<Self> {
if points.len() != width * height {
return None;
}
let is_dense = points.iter().all(|p| p.is_some());
Some(Self { width, height, points, is_dense })
}
#[inline]
pub fn len(&self) -> usize {
self.points.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn valid_count(&self) -> usize {
self.points.iter().filter(|p| p.is_some()).count()
}
#[inline]
fn idx(&self, row: usize, col: usize) -> Option<usize> {
if row < self.height && col < self.width {
Some(row * self.width + col)
} else {
None
}
}
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
self.idx(row, col).and_then(|i| self.points[i].as_ref())
}
pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> {
let i = self.idx(row, col)?;
self.points[i].as_mut()
}
pub fn set(&mut self, row: usize, col: usize, value: Option<T>) -> bool {
let Some(i) = self.idx(row, col) else { return false };
if value.is_none() {
self.is_dense = false;
}
self.points[i] = value;
true
}
pub fn row(&self, row: usize) -> &[Option<T>] {
if row >= self.height {
return &[];
}
let start = row * self.width;
&self.points[start..start + self.width]
}
pub fn ring(&self, ring_index: usize) -> &[Option<T>] {
self.row(ring_index)
}
pub fn iter_valid(&self) -> impl Iterator<Item = (usize, usize, &T)> {
let width = self.width;
self.points.iter().enumerate().filter_map(move |(i, opt)| {
opt.as_ref().map(|p| (i / width, i % width, p))
})
}
pub fn refresh_dense_flag(&mut self) {
self.is_dense = self.points.iter().all(|p| p.is_some());
}
}
impl<T: Clone> OrganizedPointCloud<T> {
pub fn to_unorganized(&self) -> PointCloud<T> {
let pts: Vec<T> = self.points.iter().filter_map(|p| p.clone()).collect();
PointCloud::from_points(pts)
}
}
impl OrganizedPointCloud<Point3f> {
pub fn from_depth_image(
depth: &[u16],
width: usize,
height: usize,
intrinsics: &CameraIntrinsics,
depth_scale: f32,
) -> Option<Self> {
if depth.len() != width * height {
return None;
}
let mut points: Vec<Option<Point3f>> = Vec::with_capacity(width * height);
let mut any_invalid = false;
for row in 0..height {
for col in 0..width {
let d = depth[row * width + col];
if d == 0 {
points.push(None);
any_invalid = true;
continue;
}
let z = d as f32 * depth_scale;
if !z.is_finite() || z <= 0.0 {
points.push(None);
any_invalid = true;
continue;
}
let x = (col as f32 - intrinsics.cx) * z / intrinsics.fx;
let y = (row as f32 - intrinsics.cy) * z / intrinsics.fy;
points.push(Some(Point3f::new(x, y, z)));
}
}
Some(Self { width, height, points, is_dense: !any_invalid })
}
}
impl<T> Default for OrganizedPointCloud<T> {
fn default() -> Self {
Self { width: 0, height: 0, points: Vec::new(), is_dense: true }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_grid_is_empty_and_not_dense() {
let cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(4, 3);
assert_eq!(cloud.width, 4);
assert_eq!(cloud.height, 3);
assert_eq!(cloud.len(), 12);
assert_eq!(cloud.valid_count(), 0);
assert!(!cloud.is_dense);
}
#[test]
fn get_set_round_trip() {
let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(3, 2);
assert!(cloud.set(1, 2, Some(Point3f::new(1.0, 2.0, 3.0))));
let p = cloud.get(1, 2).unwrap();
assert_eq!(*p, Point3f::new(1.0, 2.0, 3.0));
assert!(cloud.get(2, 0).is_none());
assert!(!cloud.set(5, 5, Some(Point3f::new(0.0, 0.0, 0.0))));
}
#[test]
fn row_alias_ring() {
let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(3, 2);
cloud.set(0, 0, Some(Point3f::new(0.0, 0.0, 0.0)));
cloud.set(0, 1, Some(Point3f::new(1.0, 0.0, 0.0)));
cloud.set(0, 2, Some(Point3f::new(2.0, 0.0, 0.0)));
let r = cloud.row(0);
assert_eq!(r.len(), 3);
assert!(r.iter().all(|p| p.is_some()));
let ring = cloud.ring(0);
assert_eq!(ring.len(), 3);
assert_eq!(cloud.row(99).len(), 0);
}
#[test]
fn to_unorganized_drops_none() {
let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(2, 2);
cloud.set(0, 0, Some(Point3f::new(1.0, 0.0, 0.0)));
cloud.set(1, 1, Some(Point3f::new(0.0, 1.0, 0.0)));
let flat = cloud.to_unorganized();
assert_eq!(flat.len(), 2);
}
#[test]
fn from_points_dense_flag() {
let pts = vec![
Some(Point3f::new(0.0, 0.0, 0.0)),
Some(Point3f::new(1.0, 0.0, 0.0)),
Some(Point3f::new(0.0, 1.0, 0.0)),
Some(Point3f::new(0.0, 0.0, 1.0)),
];
let cloud = OrganizedPointCloud::from_points(2, 2, pts).unwrap();
assert!(cloud.is_dense);
assert_eq!(cloud.valid_count(), 4);
let bad = OrganizedPointCloud::<Point3f>::from_points(2, 2, vec![None; 3]);
assert!(bad.is_none());
}
#[test]
fn from_depth_image_basic() {
let depth: Vec<u16> = vec![1000, 0, 2000, 1500];
let intr = CameraIntrinsics::new(525.0, 525.0, 0.5, 0.5);
let cloud = OrganizedPointCloud::from_depth_image(&depth, 2, 2, &intr, 0.001).unwrap();
assert_eq!(cloud.width, 2);
assert_eq!(cloud.height, 2);
assert!(!cloud.is_dense);
assert!(cloud.get(0, 1).is_none());
let p = cloud.get(0, 0).unwrap();
assert!((p.z - 1.0).abs() < 1e-6);
assert!((p.x + 0.5 / 525.0).abs() < 1e-6);
}
#[test]
fn iter_valid_yields_indexed_points() {
let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(2, 2);
cloud.set(1, 0, Some(Point3f::new(7.0, 0.0, 0.0)));
let collected: Vec<(usize, usize)> = cloud.iter_valid().map(|(r, c, _)| (r, c)).collect();
assert_eq!(collected, vec![(1, 0)]);
}
}