1use crate::error::Error;
2use chrono::{DateTime, Utc};
3use std::collections::{HashMap, HashSet};
4
5use crate::{PointCloudInfo, PointDataColumnType, PointDataColumns};
6
7use ecoord::{FrameId, ReferenceFrames};
8use nalgebra;
9use nalgebra::Point3;
10
11use polars::prelude::DataFrame;
12
13use crate::Error::{
14 MultipleFrameIdDefinitions, NoFrameIdDefinition, NoFrameIdDefinitions, NoIdColumn,
15};
16use crate::point_data::PointData;
17use polars::prelude::*;
18
19#[derive(Debug, Clone, PartialEq)]
20pub struct PointCloud {
21 pub point_data: PointData,
22 pub info: PointCloudInfo,
23 pub reference_frames: ReferenceFrames,
24}
25
26impl PointCloud {
27 pub fn new(
28 point_data: PointDataColumns,
29 info: PointCloudInfo,
30 reference_frames: ReferenceFrames,
31 ) -> Result<Self, Error> {
32 let point_data = point_data.get_as_data_frame();
33 if point_data
34 .column(PointDataColumnType::FrameId.as_str())
35 .is_ok()
36 && info.frame_id.is_some()
37 {
38 return Err(MultipleFrameIdDefinitions);
39 }
40
41 let point_data_new = PointData::new(point_data)?; Ok(Self {
44 point_data: point_data_new,
45 info,
46 reference_frames,
47 })
48 }
49
50 pub fn from_data_frame(
51 point_data: DataFrame,
52 info: PointCloudInfo,
53 reference_frames: ReferenceFrames,
54 ) -> Result<Self, Error> {
55 if point_data
56 .column(PointDataColumnType::FrameId.as_str())
57 .is_ok()
58 && info.frame_id.is_some()
59 {
60 return Err(MultipleFrameIdDefinitions);
61 }
62 let point_data_new = PointData::new(point_data)?; Ok(Self {
65 point_data: point_data_new,
66 info,
67 reference_frames,
68 })
69 }
70}
71
72impl PointCloud {
73 pub fn point_data(&self) -> &PointData {
74 &self.point_data
75 }
76
77 pub fn info(&self) -> &PointCloudInfo {
78 &self.info
79 }
80
81 pub fn reference_frames(&self) -> &ReferenceFrames {
82 &self.reference_frames
83 }
84
85 pub fn size(&self) -> usize {
86 self.point_data.height()
87 }
88
89 pub fn info_frame_id(&self) -> Option<&FrameId> {
90 self.info.frame_id.as_ref()
91 }
92
93 pub fn get_distinct_frame_ids(&self) -> Option<HashSet<FrameId>> {
94 if let Some(frame_id) = &self.info.frame_id {
95 let frame_ids = HashSet::from([frame_id.clone()]);
96 return Some(frame_ids);
97 }
98
99 if self.point_data.contains_frame_id_column() {
100 let frame_ids = self
101 .point_data
102 .get_distinct_frame_ids()
103 .expect("must be derivable");
104 return Some(frame_ids);
105 }
106
107 None
108 }
109}
110
111impl PointCloud {
112 pub fn contains_ids(&self) -> bool {
113 self.point_data.contains_id_column()
114 }
115
116 pub fn contains_frame_ids(&self) -> bool {
117 self.info.frame_id.is_some() || !self.point_data.contains_frame_id_column()
118 }
119 pub fn contains_timestamps(&self) -> bool {
120 self.point_data.contains_timestamps()
121 }
122
123 pub fn contains_sensor_translation(&self) -> bool {
124 self.point_data.contains_sensor_translation()
125 }
126
127 pub fn contains_colors(&self) -> bool {
128 self.point_data.contains_colors()
129 }
130
131 pub fn set_reference_frames(&mut self, reference_frames: ReferenceFrames) {
132 self.reference_frames = reference_frames;
133 }
134
135 pub fn set_info_frame_id(&mut self, frame_id: Option<FrameId>) {
136 self.info.frame_id = frame_id;
137 }
138
139 pub fn update_points(
140 &mut self,
141 points: Vec<Point3<f64>>,
142 frame_id: Option<FrameId>,
143 ) -> Result<(), Error> {
144 self.point_data.update_points_in_place(points)?;
145 self.info.frame_id = frame_id;
146
147 Ok(())
148 }
149
150 pub fn derive_spherical_points(&mut self) -> Result<(), Error> {
151 self.point_data.derive_spherical_points()?;
152
153 Ok(())
154 }
155
156 pub fn filter_by_id_range(
157 &self,
158 id_min: Option<u64>,
159 id_max: Option<u64>,
160 ) -> Result<PointCloud, Error> {
161 if !self.contains_ids() {
162 return Err(NoIdColumn);
163 }
164
165 let mut filter_predicate = col(PointDataColumnType::Id.as_str());
166 if let Some(id_min) = id_min {
167 filter_predicate = filter_predicate.gt_eq(lit(id_min));
168 }
169 if let Some(id_max) = id_max {
170 filter_predicate =
171 filter_predicate.and(col(PointDataColumnType::Id.as_str()).lt_eq(id_max));
172 }
173
174 let point_data = self
175 .point_data
176 .data_frame
177 .clone()
178 .lazy()
179 .filter(filter_predicate)
180 .collect()?;
181
182 let filtered_point_cloud = PointCloud::from_data_frame(
183 point_data,
184 self.info.clone(),
185 self.reference_frames.clone(),
186 )?;
187 Ok(filtered_point_cloud)
188 }
189
190 pub fn filter_by_frame_id(&self, frame_id: &FrameId) -> Result<PointCloud, Error> {
191 if !self
192 .get_distinct_frame_ids()
193 .ok_or(NoFrameIdDefinitions)?
194 .contains(frame_id)
195 {
196 return Err(NoFrameIdDefinition(frame_id.clone()));
197 }
198
199 let filter_predicate = col(PointDataColumnType::FrameId.as_str())
200 .cast(DataType::String)
201 .eq(lit(frame_id.clone().to_string().as_str()));
202
203 let point_data = self
204 .point_data
205 .data_frame
206 .clone()
207 .lazy()
208 .filter(filter_predicate)
209 .collect()?;
210
211 let filtered_point_cloud = PointCloud::from_data_frame(
212 point_data,
213 self.info.clone(),
214 self.reference_frames.clone(),
215 )?;
216 Ok(filtered_point_cloud)
217 }
218
219 pub fn filter_by_row_indices(&self, row_indices: HashSet<usize>) -> Result<PointCloud, Error> {
220 let filtered_point_data = self.point_data.filter_by_row_indices(row_indices)?;
221
222 let filtered_point_cloud = PointCloud::from_data_frame(
223 filtered_point_data.data_frame,
224 self.info.clone(),
225 self.reference_frames.clone(),
226 )?;
227 Ok(filtered_point_cloud)
228 }
229
230 pub fn filter_by_boolean_mask(&self, mask: &Vec<bool>) -> Result<PointCloud, Error> {
231 let mask_series: Series = mask.iter().collect();
232 let filtered_point_data = self
233 .point_data
234 .filter_by_boolean_mask(mask_series.bool()?)?;
235
236 let filtered_point_cloud = PointCloud::from_data_frame(
237 filtered_point_data.data_frame,
238 self.info.clone(),
239 self.reference_frames.clone(),
240 )?;
241 Ok(filtered_point_cloud)
242 }
243
244 pub fn filter_by_bounds(
245 &self,
246 bound_min: Point3<f64>,
247 bound_max: Point3<f64>,
248 ) -> Result<Option<PointCloud>, Error> {
249 let filtered_point_data = self.point_data.filter_by_bounds(bound_min, bound_max)?;
250
251 let result = if let Some(filtered_point_data) = filtered_point_data {
252 let filtered_point_cloud = PointCloud::from_data_frame(
253 filtered_point_data.data_frame,
254 self.info.clone(),
255 self.reference_frames.clone(),
256 )?;
257 Some(filtered_point_cloud)
258 } else {
259 None
260 };
261
262 Ok(result)
263 }
264
265 pub fn filter_by_beam_length(
266 &self,
267 beam_length_min: f64,
268 beam_length_max: f64,
269 ) -> Result<Option<PointCloud>, Error> {
270 let filtered_point_data = self
271 .point_data
272 .filter_by_beam_length(beam_length_min, beam_length_max)?;
273
274 let result = if let Some(filtered_point_data) = filtered_point_data {
275 let filtered_point_cloud = PointCloud::from_data_frame(
276 filtered_point_data.data_frame,
277 self.info.clone(),
278 self.reference_frames.clone(),
279 )?;
280 Some(filtered_point_cloud)
281 } else {
282 None
283 };
284
285 Ok(result)
286 }
287
288 pub fn filter_by_x_min(&self, x_min: f64) -> Result<Option<PointCloud>, Error> {
289 let filtered_point_data = self.point_data.filter_by_x_min(x_min)?;
290
291 let result = if let Some(filtered_point_data) = filtered_point_data {
292 let filtered_point_cloud = PointCloud::from_data_frame(
293 filtered_point_data.data_frame,
294 self.info.clone(),
295 self.reference_frames.clone(),
296 )?;
297 Some(filtered_point_cloud)
298 } else {
299 None
300 };
301
302 Ok(result)
303 }
304
305 pub fn filter_by_x_max(&self, x_max: f64) -> Result<Option<PointCloud>, Error> {
306 let filtered_point_data = self.point_data.filter_by_x_max(x_max)?;
307
308 let result = if let Some(filtered_point_data) = filtered_point_data {
309 let filtered_point_cloud = PointCloud::from_data_frame(
310 filtered_point_data.data_frame,
311 self.info.clone(),
312 self.reference_frames.clone(),
313 )?;
314 Some(filtered_point_cloud)
315 } else {
316 None
317 };
318
319 Ok(result)
320 }
321
322 pub fn filter_by_y_min(&self, y_min: f64) -> Result<Option<PointCloud>, Error> {
323 let filtered_point_data = self.point_data.filter_by_y_min(y_min)?;
324
325 let result = if let Some(filtered_point_data) = filtered_point_data {
326 let filtered_point_cloud = PointCloud::from_data_frame(
327 filtered_point_data.data_frame,
328 self.info.clone(),
329 self.reference_frames.clone(),
330 )?;
331 Some(filtered_point_cloud)
332 } else {
333 None
334 };
335
336 Ok(result)
337 }
338
339 pub fn filter_by_y_max(&self, y_max: f64) -> Result<Option<PointCloud>, Error> {
340 let filtered_point_data = self.point_data.filter_by_y_max(y_max)?;
341
342 let result = if let Some(filtered_point_data) = filtered_point_data {
343 let filtered_point_cloud = PointCloud::from_data_frame(
344 filtered_point_data.data_frame,
345 self.info.clone(),
346 self.reference_frames.clone(),
347 )?;
348 Some(filtered_point_cloud)
349 } else {
350 None
351 };
352
353 Ok(result)
354 }
355
356 pub fn filter_by_z_min(&self, z_min: f64) -> Result<Option<PointCloud>, Error> {
357 let filtered_point_data = self.point_data.filter_by_z_min(z_min)?;
358
359 let result = if let Some(filtered_point_data) = filtered_point_data {
360 let filtered_point_cloud = PointCloud::from_data_frame(
361 filtered_point_data.data_frame,
362 self.info.clone(),
363 self.reference_frames.clone(),
364 )?;
365 Some(filtered_point_cloud)
366 } else {
367 None
368 };
369
370 Ok(result)
371 }
372
373 pub fn filter_by_z_max(&self, z_max: f64) -> Result<Option<PointCloud>, Error> {
374 let filtered_point_data = self.point_data.filter_by_z_max(z_max)?;
375
376 let result = if let Some(filtered_point_data) = filtered_point_data {
377 let filtered_point_cloud = PointCloud::from_data_frame(
378 filtered_point_data.data_frame,
379 self.info.clone(),
380 self.reference_frames.clone(),
381 )?;
382 Some(filtered_point_cloud)
383 } else {
384 None
385 };
386
387 Ok(result)
388 }
389
390 pub fn filter_by_spherical_range_min(
391 &self,
392 spherical_range_min: f64,
393 ) -> Result<Option<PointCloud>, Error> {
394 let filtered_point_data = self
395 .point_data
396 .filter_by_spherical_range_min(spherical_range_min)?;
397
398 let result = if let Some(filtered_point_data) = filtered_point_data {
399 let filtered_point_cloud = PointCloud::from_data_frame(
400 filtered_point_data.data_frame,
401 self.info.clone(),
402 self.reference_frames.clone(),
403 )?;
404 Some(filtered_point_cloud)
405 } else {
406 None
407 };
408
409 Ok(result)
410 }
411
412 pub fn filter_by_spherical_range_max(
413 &self,
414 spherical_range_max: f64,
415 ) -> Result<Option<PointCloud>, Error> {
416 let filtered_point_data = self
417 .point_data
418 .filter_by_spherical_range_max(spherical_range_max)?;
419
420 let result = if let Some(filtered_point_data) = filtered_point_data {
421 let filtered_point_cloud = PointCloud::from_data_frame(
422 filtered_point_data.data_frame,
423 self.info.clone(),
424 self.reference_frames.clone(),
425 )?;
426 Some(filtered_point_cloud)
427 } else {
428 None
429 };
430
431 Ok(result)
432 }
433}
434
435impl PointCloud {
436 pub fn append_reference_frames(
437 &mut self,
438 reference_frames: ReferenceFrames,
439 ) -> Result<(), ecoord::Error> {
440 let merged_reference_frames =
441 ecoord::merge(&[self.reference_frames.clone(), reference_frames])?;
442 self.set_reference_frames(merged_reference_frames);
443 Ok(())
444 }
445
446 pub fn resolve_to_frame(&mut self, target_frame_id: FrameId) -> Result<(), Error> {
453 if self.info.frame_id.is_none() && !self.point_data.contains_frame_id_column() {
454 return Err(NoFrameIdDefinitions);
455 }
456
457 let mut partition_columns: Vec<&str> = Vec::new();
458 if self.point_data.contains_frame_id_column() {
459 partition_columns.push(PointDataColumnType::FrameId.as_str());
460 }
461 if self.point_data.contains_timestamp_sec_column() {
462 partition_columns.push(PointDataColumnType::TimestampSeconds.as_str());
463 }
464 if self.point_data.contains_timestamp_nanosec_column() {
465 partition_columns.push(PointDataColumnType::TimestampNanoSeconds.as_str());
466 }
467
468 let partitioned_data_frames: Vec<DataFrame> = if partition_columns.is_empty() {
469 vec![self.point_data.data_frame.clone()]
470 } else {
471 self.point_data
472 .data_frame
473 .clone()
474 .partition_by(partition_columns, true)?
475 };
476
477 let partitioned_data_frames: HashMap<(FrameId, Option<DateTime<Utc>>), DataFrame> =
478 partitioned_data_frames
479 .into_iter()
480 .map(|df| {
481 let point_data = PointData::new_unchecked(df);
482 let frame_ids_series = point_data.get_all_frame_ids();
484 let frame_id = frame_ids_series.map_or_else(
485 |_| self.info.frame_id.clone().unwrap(),
486 |f| f.first().unwrap().clone(),
487 );
488
489 let timestamp = point_data
490 .get_all_timestamps()
491 .ok()
492 .map(|t| *t.first().unwrap());
493 ((frame_id, timestamp), point_data.data_frame)
494 })
495 .collect();
496
497 let partitioned_data_frames: Vec<DataFrame> = partitioned_data_frames
498 .iter()
499 .map(|((current_frame_id, current_timestamp), df)| {
500 let mut point_data = PointData::new_unchecked(df.clone());
501 let r = point_data.resolve_data_frame(
502 &self.reference_frames,
503 current_timestamp,
504 current_frame_id,
505 &target_frame_id,
506 );
507 if r.is_err() {
508 print!(
509 "should work: current_frame_id = {current_frame_id:?}, target_frame_id: {target_frame_id:?}"
510 );
511 println!(
512 "all available frame ids: {:?}",
513 &self.reference_frames.get_frame_ids()
514 );
515 panic!("error");
516 }
517
518 point_data.data_frame
519 })
520 .collect();
521
522 let partitioned_lazy: Vec<LazyFrame> = partitioned_data_frames
523 .iter()
524 .map(|d| d.clone().lazy())
525 .collect();
526
527 let mut merged_again = concat(partitioned_lazy, Default::default())
528 .expect("concatenation should work")
529 .collect()
530 .expect("concatenation should work");
531
532 if merged_again
534 .column(PointDataColumnType::TimestampSeconds.as_str())
535 .is_ok()
536 && merged_again
537 .column(PointDataColumnType::TimestampNanoSeconds.as_str())
538 .is_ok()
539 && merged_again
540 .column(PointDataColumnType::FrameId.as_str())
541 .is_err()
542 {
543 merged_again = merged_again
544 .sort(
545 [
546 PointDataColumnType::TimestampSeconds.as_str(),
547 PointDataColumnType::TimestampNanoSeconds.as_str(),
548 ],
549 SortMultipleOptions::default().with_maintain_order(true),
550 )
551 .expect("");
552 }
553 if merged_again
555 .column(PointDataColumnType::Id.as_str())
556 .is_ok()
557 {
558 merged_again = merged_again
559 .sort(
560 [PointDataColumnType::Id.as_str()],
561 SortMultipleOptions::default().with_maintain_order(true),
562 )
563 .expect("");
564 }
565
566 self.point_data.data_frame = merged_again;
567 self.info.frame_id = Some(target_frame_id);
568
569 Ok(())
570 }
571}