1use crate::point::Point3f;
17use crate::point_cloud::PointCloud;
18use serde::{Deserialize, Serialize};
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct OrganizedPointCloud<T> {
26 pub width: usize,
28 pub height: usize,
30 pub points: Vec<Option<T>>,
32 pub is_dense: bool,
34}
35
36#[derive(Debug, Clone, Copy)]
38pub struct CameraIntrinsics {
39 pub fx: f32,
41 pub fy: f32,
43 pub cx: f32,
45 pub cy: f32,
47}
48
49impl CameraIntrinsics {
50 pub fn new(fx: f32, fy: f32, cx: f32, cy: f32) -> Self {
52 Self { fx, fy, cx, cy }
53 }
54}
55
56impl<T> OrganizedPointCloud<T> {
57 pub fn new(width: usize, height: usize) -> Self
60 where
61 T: Clone,
62 {
63 let mut points = Vec::with_capacity(width * height);
64 points.resize_with(width * height, || None);
65 Self { width, height, points, is_dense: false }
66 }
67
68 pub fn from_points(width: usize, height: usize, points: Vec<Option<T>>) -> Option<Self> {
72 if points.len() != width * height {
73 return None;
74 }
75 let is_dense = points.iter().all(|p| p.is_some());
76 Some(Self { width, height, points, is_dense })
77 }
78
79 #[inline]
81 pub fn len(&self) -> usize {
82 self.points.len()
83 }
84
85 #[inline]
87 pub fn is_empty(&self) -> bool {
88 self.points.is_empty()
89 }
90
91 pub fn valid_count(&self) -> usize {
93 self.points.iter().filter(|p| p.is_some()).count()
94 }
95
96 #[inline]
98 fn idx(&self, row: usize, col: usize) -> Option<usize> {
99 if row < self.height && col < self.width {
100 Some(row * self.width + col)
101 } else {
102 None
103 }
104 }
105
106 pub fn get(&self, row: usize, col: usize) -> Option<&T> {
109 self.idx(row, col).and_then(|i| self.points[i].as_ref())
110 }
111
112 pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> {
114 let i = self.idx(row, col)?;
115 self.points[i].as_mut()
116 }
117
118 pub fn set(&mut self, row: usize, col: usize, value: Option<T>) -> bool {
121 let Some(i) = self.idx(row, col) else { return false };
122 if value.is_none() {
123 self.is_dense = false;
124 }
125 self.points[i] = value;
126 true
127 }
128
129 pub fn row(&self, row: usize) -> &[Option<T>] {
131 if row >= self.height {
132 return &[];
133 }
134 let start = row * self.width;
135 &self.points[start..start + self.width]
136 }
137
138 pub fn ring(&self, ring_index: usize) -> &[Option<T>] {
140 self.row(ring_index)
141 }
142
143 pub fn iter_valid(&self) -> impl Iterator<Item = (usize, usize, &T)> {
145 let width = self.width;
146 self.points.iter().enumerate().filter_map(move |(i, opt)| {
147 opt.as_ref().map(|p| (i / width, i % width, p))
148 })
149 }
150
151 pub fn refresh_dense_flag(&mut self) {
153 self.is_dense = self.points.iter().all(|p| p.is_some());
154 }
155}
156
157impl<T: Clone> OrganizedPointCloud<T> {
158 pub fn to_unorganized(&self) -> PointCloud<T> {
160 let pts: Vec<T> = self.points.iter().filter_map(|p| p.clone()).collect();
161 PointCloud::from_points(pts)
162 }
163}
164
165impl OrganizedPointCloud<Point3f> {
166 pub fn from_depth_image(
172 depth: &[u16],
173 width: usize,
174 height: usize,
175 intrinsics: &CameraIntrinsics,
176 depth_scale: f32,
177 ) -> Option<Self> {
178 if depth.len() != width * height {
179 return None;
180 }
181 let mut points: Vec<Option<Point3f>> = Vec::with_capacity(width * height);
182 let mut any_invalid = false;
183 for row in 0..height {
184 for col in 0..width {
185 let d = depth[row * width + col];
186 if d == 0 {
187 points.push(None);
188 any_invalid = true;
189 continue;
190 }
191 let z = d as f32 * depth_scale;
192 if !z.is_finite() || z <= 0.0 {
193 points.push(None);
194 any_invalid = true;
195 continue;
196 }
197 let x = (col as f32 - intrinsics.cx) * z / intrinsics.fx;
198 let y = (row as f32 - intrinsics.cy) * z / intrinsics.fy;
199 points.push(Some(Point3f::new(x, y, z)));
200 }
201 }
202 Some(Self { width, height, points, is_dense: !any_invalid })
203 }
204}
205
206impl<T> Default for OrganizedPointCloud<T> {
207 fn default() -> Self {
208 Self { width: 0, height: 0, points: Vec::new(), is_dense: true }
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn new_grid_is_empty_and_not_dense() {
218 let cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(4, 3);
219 assert_eq!(cloud.width, 4);
220 assert_eq!(cloud.height, 3);
221 assert_eq!(cloud.len(), 12);
222 assert_eq!(cloud.valid_count(), 0);
223 assert!(!cloud.is_dense);
224 }
225
226 #[test]
227 fn get_set_round_trip() {
228 let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(3, 2);
229 assert!(cloud.set(1, 2, Some(Point3f::new(1.0, 2.0, 3.0))));
230 let p = cloud.get(1, 2).unwrap();
231 assert_eq!(*p, Point3f::new(1.0, 2.0, 3.0));
232 assert!(cloud.get(2, 0).is_none());
234 assert!(!cloud.set(5, 5, Some(Point3f::new(0.0, 0.0, 0.0))));
235 }
236
237 #[test]
238 fn row_alias_ring() {
239 let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(3, 2);
240 cloud.set(0, 0, Some(Point3f::new(0.0, 0.0, 0.0)));
241 cloud.set(0, 1, Some(Point3f::new(1.0, 0.0, 0.0)));
242 cloud.set(0, 2, Some(Point3f::new(2.0, 0.0, 0.0)));
243 let r = cloud.row(0);
244 assert_eq!(r.len(), 3);
245 assert!(r.iter().all(|p| p.is_some()));
246 let ring = cloud.ring(0);
247 assert_eq!(ring.len(), 3);
248 assert_eq!(cloud.row(99).len(), 0);
250 }
251
252 #[test]
253 fn to_unorganized_drops_none() {
254 let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(2, 2);
255 cloud.set(0, 0, Some(Point3f::new(1.0, 0.0, 0.0)));
256 cloud.set(1, 1, Some(Point3f::new(0.0, 1.0, 0.0)));
257 let flat = cloud.to_unorganized();
258 assert_eq!(flat.len(), 2);
259 }
260
261 #[test]
262 fn from_points_dense_flag() {
263 let pts = vec![
264 Some(Point3f::new(0.0, 0.0, 0.0)),
265 Some(Point3f::new(1.0, 0.0, 0.0)),
266 Some(Point3f::new(0.0, 1.0, 0.0)),
267 Some(Point3f::new(0.0, 0.0, 1.0)),
268 ];
269 let cloud = OrganizedPointCloud::from_points(2, 2, pts).unwrap();
270 assert!(cloud.is_dense);
271 assert_eq!(cloud.valid_count(), 4);
272
273 let bad = OrganizedPointCloud::<Point3f>::from_points(2, 2, vec![None; 3]);
274 assert!(bad.is_none());
275 }
276
277 #[test]
278 fn from_depth_image_basic() {
279 let depth: Vec<u16> = vec![1000, 0, 2000, 1500];
281 let intr = CameraIntrinsics::new(525.0, 525.0, 0.5, 0.5);
282 let cloud = OrganizedPointCloud::from_depth_image(&depth, 2, 2, &intr, 0.001).unwrap();
283 assert_eq!(cloud.width, 2);
284 assert_eq!(cloud.height, 2);
285 assert!(!cloud.is_dense);
286 assert!(cloud.get(0, 1).is_none());
288 let p = cloud.get(0, 0).unwrap();
290 assert!((p.z - 1.0).abs() < 1e-6);
291 assert!((p.x + 0.5 / 525.0).abs() < 1e-6);
292 }
293
294 #[test]
295 fn iter_valid_yields_indexed_points() {
296 let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(2, 2);
297 cloud.set(1, 0, Some(Point3f::new(7.0, 0.0, 0.0)));
298 let collected: Vec<(usize, usize)> = cloud.iter_valid().map(|(r, c, _)| (r, c)).collect();
299 assert_eq!(collected, vec![(1, 0)]);
300 }
301}