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 {
66 width,
67 height,
68 points,
69 is_dense: false,
70 }
71 }
72
73 pub fn from_points(width: usize, height: usize, points: Vec<Option<T>>) -> Option<Self> {
77 if points.len() != width * height {
78 return None;
79 }
80 let is_dense = points.iter().all(|p| p.is_some());
81 Some(Self {
82 width,
83 height,
84 points,
85 is_dense,
86 })
87 }
88
89 #[inline]
91 pub fn len(&self) -> usize {
92 self.points.len()
93 }
94
95 #[inline]
97 pub fn is_empty(&self) -> bool {
98 self.points.is_empty()
99 }
100
101 pub fn valid_count(&self) -> usize {
103 self.points.iter().filter(|p| p.is_some()).count()
104 }
105
106 #[inline]
108 fn idx(&self, row: usize, col: usize) -> Option<usize> {
109 if row < self.height && col < self.width {
110 Some(row * self.width + col)
111 } else {
112 None
113 }
114 }
115
116 pub fn get(&self, row: usize, col: usize) -> Option<&T> {
119 self.idx(row, col).and_then(|i| self.points[i].as_ref())
120 }
121
122 pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> {
124 let i = self.idx(row, col)?;
125 self.points[i].as_mut()
126 }
127
128 pub fn set(&mut self, row: usize, col: usize, value: Option<T>) -> bool {
131 let Some(i) = self.idx(row, col) else {
132 return false;
133 };
134 if value.is_none() {
135 self.is_dense = false;
136 }
137 self.points[i] = value;
138 true
139 }
140
141 pub fn row(&self, row: usize) -> &[Option<T>] {
143 if row >= self.height {
144 return &[];
145 }
146 let start = row * self.width;
147 &self.points[start..start + self.width]
148 }
149
150 pub fn ring(&self, ring_index: usize) -> &[Option<T>] {
152 self.row(ring_index)
153 }
154
155 pub fn iter_valid(&self) -> impl Iterator<Item = (usize, usize, &T)> {
157 let width = self.width;
158 self.points
159 .iter()
160 .enumerate()
161 .filter_map(move |(i, opt)| opt.as_ref().map(|p| (i / width, i % width, p)))
162 }
163
164 pub fn refresh_dense_flag(&mut self) {
166 self.is_dense = self.points.iter().all(|p| p.is_some());
167 }
168}
169
170impl<T: Clone> OrganizedPointCloud<T> {
171 pub fn to_unorganized(&self) -> PointCloud<T> {
173 let pts: Vec<T> = self.points.iter().filter_map(|p| p.clone()).collect();
174 PointCloud::from_points(pts)
175 }
176}
177
178impl OrganizedPointCloud<Point3f> {
179 pub fn from_depth_image(
185 depth: &[u16],
186 width: usize,
187 height: usize,
188 intrinsics: &CameraIntrinsics,
189 depth_scale: f32,
190 ) -> Option<Self> {
191 if depth.len() != width * height {
192 return None;
193 }
194 let mut points: Vec<Option<Point3f>> = Vec::with_capacity(width * height);
195 let mut any_invalid = false;
196 for row in 0..height {
197 for col in 0..width {
198 let d = depth[row * width + col];
199 if d == 0 {
200 points.push(None);
201 any_invalid = true;
202 continue;
203 }
204 let z = d as f32 * depth_scale;
205 if !z.is_finite() || z <= 0.0 {
206 points.push(None);
207 any_invalid = true;
208 continue;
209 }
210 let x = (col as f32 - intrinsics.cx) * z / intrinsics.fx;
211 let y = (row as f32 - intrinsics.cy) * z / intrinsics.fy;
212 points.push(Some(Point3f::new(x, y, z)));
213 }
214 }
215 Some(Self {
216 width,
217 height,
218 points,
219 is_dense: !any_invalid,
220 })
221 }
222}
223
224impl<T> Default for OrganizedPointCloud<T> {
225 fn default() -> Self {
226 Self {
227 width: 0,
228 height: 0,
229 points: Vec::new(),
230 is_dense: true,
231 }
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn new_grid_is_empty_and_not_dense() {
241 let cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(4, 3);
242 assert_eq!(cloud.width, 4);
243 assert_eq!(cloud.height, 3);
244 assert_eq!(cloud.len(), 12);
245 assert_eq!(cloud.valid_count(), 0);
246 assert!(!cloud.is_dense);
247 }
248
249 #[test]
250 fn get_set_round_trip() {
251 let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(3, 2);
252 assert!(cloud.set(1, 2, Some(Point3f::new(1.0, 2.0, 3.0))));
253 let p = cloud.get(1, 2).unwrap();
254 assert_eq!(*p, Point3f::new(1.0, 2.0, 3.0));
255 assert!(cloud.get(2, 0).is_none());
257 assert!(!cloud.set(5, 5, Some(Point3f::new(0.0, 0.0, 0.0))));
258 }
259
260 #[test]
261 fn row_alias_ring() {
262 let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(3, 2);
263 cloud.set(0, 0, Some(Point3f::new(0.0, 0.0, 0.0)));
264 cloud.set(0, 1, Some(Point3f::new(1.0, 0.0, 0.0)));
265 cloud.set(0, 2, Some(Point3f::new(2.0, 0.0, 0.0)));
266 let r = cloud.row(0);
267 assert_eq!(r.len(), 3);
268 assert!(r.iter().all(|p| p.is_some()));
269 let ring = cloud.ring(0);
270 assert_eq!(ring.len(), 3);
271 assert_eq!(cloud.row(99).len(), 0);
273 }
274
275 #[test]
276 fn to_unorganized_drops_none() {
277 let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(2, 2);
278 cloud.set(0, 0, Some(Point3f::new(1.0, 0.0, 0.0)));
279 cloud.set(1, 1, Some(Point3f::new(0.0, 1.0, 0.0)));
280 let flat = cloud.to_unorganized();
281 assert_eq!(flat.len(), 2);
282 }
283
284 #[test]
285 fn from_points_dense_flag() {
286 let pts = vec![
287 Some(Point3f::new(0.0, 0.0, 0.0)),
288 Some(Point3f::new(1.0, 0.0, 0.0)),
289 Some(Point3f::new(0.0, 1.0, 0.0)),
290 Some(Point3f::new(0.0, 0.0, 1.0)),
291 ];
292 let cloud = OrganizedPointCloud::from_points(2, 2, pts).unwrap();
293 assert!(cloud.is_dense);
294 assert_eq!(cloud.valid_count(), 4);
295
296 let bad = OrganizedPointCloud::<Point3f>::from_points(2, 2, vec![None; 3]);
297 assert!(bad.is_none());
298 }
299
300 #[test]
301 fn from_depth_image_basic() {
302 let depth: Vec<u16> = vec![1000, 0, 2000, 1500];
304 let intr = CameraIntrinsics::new(525.0, 525.0, 0.5, 0.5);
305 let cloud = OrganizedPointCloud::from_depth_image(&depth, 2, 2, &intr, 0.001).unwrap();
306 assert_eq!(cloud.width, 2);
307 assert_eq!(cloud.height, 2);
308 assert!(!cloud.is_dense);
309 assert!(cloud.get(0, 1).is_none());
311 let p = cloud.get(0, 0).unwrap();
313 assert!((p.z - 1.0).abs() < 1e-6);
314 assert!((p.x + 0.5 / 525.0).abs() < 1e-6);
315 }
316
317 #[test]
318 fn iter_valid_yields_indexed_points() {
319 let mut cloud: OrganizedPointCloud<Point3f> = OrganizedPointCloud::new(2, 2);
320 cloud.set(1, 0, Some(Point3f::new(7.0, 0.0, 0.0)));
321 let collected: Vec<(usize, usize)> = cloud.iter_valid().map(|(r, c, _)| (r, c)).collect();
322 assert_eq!(collected, vec![(1, 0)]);
323 }
324}