1use crate::Error;
2use crate::Error::{
3 ColumnAlreadyExists, LowerBoundEqualsUpperBound, LowerBoundExceedsUpperBound, NoData,
4 ObligatoryColumn, ShapeMismatch, TypeMismatch,
5};
6use chrono::{DateTime, TimeZone, Utc};
7use ecoord::octree::OctantIndex;
8use ecoord::{AxisAlignedBoundingBox, FrameId, SphericalPoint3, TransformId, TransformTree};
9use nalgebra::{Isometry3, Point3, Quaternion, UnitQuaternion};
10use palette::Srgb;
11use parry3d_f64::shape::ConvexPolyhedron;
12use polars::prelude::*;
13use rayon::prelude::*;
14use std::collections::HashSet;
15use std::ops::{Add, Sub};
16use std::str::FromStr;
17
18const COLUMN_NAME_X_STR: &str = "x";
19const COLUMN_NAME_Y_STR: &str = "y";
20const COLUMN_NAME_Z_STR: &str = "z";
21const COLUMN_NAME_ID_STR: &str = "id";
22const COLUMN_NAME_FRAME_ID_STR: &str = "frame_id";
23const COLUMN_NAME_TIMESTAMP_SEC_STR: &str = "timestamp_sec";
24const COLUMN_NAME_TIMESTAMP_NANOSEC_STR: &str = "timestamp_nanosec";
25const COLUMN_NAME_INTENSITY_STR: &str = "intensity";
26const COLUMN_NAME_SENSOR_TRANSLATION_X_STR: &str = "sensor_translation_x";
27const COLUMN_NAME_SENSOR_TRANSLATION_Y_STR: &str = "sensor_translation_y";
28const COLUMN_NAME_SENSOR_TRANSLATION_Z_STR: &str = "sensor_translation_z";
29const COLUMN_NAME_SENSOR_ROTATION_X_STR: &str = "sensor_rotation_x";
30const COLUMN_NAME_SENSOR_ROTATION_Y_STR: &str = "sensor_rotation_y";
31const COLUMN_NAME_SENSOR_ROTATION_Z_STR: &str = "sensor_rotation_z";
32const COLUMN_NAME_SENSOR_ROTATION_W_STR: &str = "sensor_rotation_w";
33const COLUMN_NAME_COLOR_RED_STR: &str = "color_red";
34const COLUMN_NAME_COLOR_GREEN_STR: &str = "color_green";
35const COLUMN_NAME_COLOR_BLUE_STR: &str = "color_blue";
36const COLUMN_NAME_SPHERICAL_AZIMUTH_STR: &str = "spherical_azimuth";
37const COLUMN_NAME_SPHERICAL_ELEVATION_STR: &str = "spherical_elevation";
38const COLUMN_NAME_SPHERICAL_RANGE_STR: &str = "spherical_range";
39const COLUMN_NAME_OCTANT_INDEX_LEVEL_STR: &str = "octant_index_level";
40const COLUMN_NAME_OCTANT_INDEX_X_STR: &str = "octant_index_x";
41const COLUMN_NAME_OCTANT_INDEX_Y_STR: &str = "octant_index_y";
42const COLUMN_NAME_OCTANT_INDEX_Z_STR: &str = "octant_index_z";
43const COLUMN_NAME_POINT_SOURCE_ID_STR: &str = "point_source_id";
44
45#[derive(Debug, Clone, Copy, Eq, PartialEq)]
46pub enum PointDataColumnType {
47 X,
49 Y,
51 Z,
53 Id,
55 FrameId,
57 TimestampSecond,
59 TimestampNanoSecond,
61 Intensity,
63 SensorTranslationX,
65 SensorTranslationY,
67 SensorTranslationZ,
69 SensorRotationX,
71 SensorRotationY,
73 SensorRotationZ,
75 SensorRotationW,
77 ColorRed,
79 ColorGreen,
81 ColorBlue,
83 SphericalAzimuth,
85 SphericalElevation,
87 SphericalRange,
89 OctantIndexLevel,
91 OctantIndexX,
93 OctantIndexY,
95 OctantIndexZ,
97 PointSourceId,
100}
101
102impl std::str::FromStr for PointDataColumnType {
103 type Err = ();
104
105 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 match s {
107 COLUMN_NAME_X_STR => Ok(PointDataColumnType::X),
108 COLUMN_NAME_Y_STR => Ok(PointDataColumnType::Y),
109 COLUMN_NAME_Z_STR => Ok(PointDataColumnType::Z),
110 COLUMN_NAME_ID_STR => Ok(PointDataColumnType::Id),
111 COLUMN_NAME_FRAME_ID_STR => Ok(PointDataColumnType::FrameId),
112 COLUMN_NAME_TIMESTAMP_SEC_STR => Ok(PointDataColumnType::TimestampSecond),
113 COLUMN_NAME_TIMESTAMP_NANOSEC_STR => Ok(PointDataColumnType::TimestampNanoSecond),
114 COLUMN_NAME_INTENSITY_STR => Ok(PointDataColumnType::Intensity),
115 COLUMN_NAME_SENSOR_TRANSLATION_X_STR => Ok(PointDataColumnType::SensorTranslationX),
116 COLUMN_NAME_SENSOR_TRANSLATION_Y_STR => Ok(PointDataColumnType::SensorTranslationY),
117 COLUMN_NAME_SENSOR_TRANSLATION_Z_STR => Ok(PointDataColumnType::SensorTranslationZ),
118 COLUMN_NAME_COLOR_RED_STR => Ok(PointDataColumnType::ColorRed),
119 COLUMN_NAME_COLOR_GREEN_STR => Ok(PointDataColumnType::ColorGreen),
120 COLUMN_NAME_COLOR_BLUE_STR => Ok(PointDataColumnType::ColorBlue),
121 COLUMN_NAME_SPHERICAL_AZIMUTH_STR => Ok(PointDataColumnType::SphericalAzimuth),
122 COLUMN_NAME_SPHERICAL_ELEVATION_STR => Ok(PointDataColumnType::SphericalElevation),
123 COLUMN_NAME_SPHERICAL_RANGE_STR => Ok(PointDataColumnType::SphericalRange),
124 COLUMN_NAME_POINT_SOURCE_ID_STR => Ok(PointDataColumnType::PointSourceId),
125 _ => Err(()),
126 }
127 }
128}
129
130impl PointDataColumnType {
131 pub fn as_str(&self) -> &'static str {
132 match self {
133 PointDataColumnType::X => COLUMN_NAME_X_STR,
134 PointDataColumnType::Y => COLUMN_NAME_Y_STR,
135 PointDataColumnType::Z => COLUMN_NAME_Z_STR,
136 PointDataColumnType::Id => COLUMN_NAME_ID_STR,
137 PointDataColumnType::FrameId => COLUMN_NAME_FRAME_ID_STR,
138 PointDataColumnType::TimestampSecond => COLUMN_NAME_TIMESTAMP_SEC_STR,
139 PointDataColumnType::TimestampNanoSecond => COLUMN_NAME_TIMESTAMP_NANOSEC_STR,
140 PointDataColumnType::Intensity => COLUMN_NAME_INTENSITY_STR,
141 PointDataColumnType::SensorTranslationX => COLUMN_NAME_SENSOR_TRANSLATION_X_STR,
142 PointDataColumnType::SensorTranslationY => COLUMN_NAME_SENSOR_TRANSLATION_Y_STR,
143 PointDataColumnType::SensorTranslationZ => COLUMN_NAME_SENSOR_TRANSLATION_Z_STR,
144 PointDataColumnType::SensorRotationX => COLUMN_NAME_SENSOR_ROTATION_X_STR,
145 PointDataColumnType::SensorRotationY => COLUMN_NAME_SENSOR_ROTATION_Y_STR,
146 PointDataColumnType::SensorRotationZ => COLUMN_NAME_SENSOR_ROTATION_Z_STR,
147 PointDataColumnType::SensorRotationW => COLUMN_NAME_SENSOR_ROTATION_W_STR,
148 PointDataColumnType::ColorRed => COLUMN_NAME_COLOR_RED_STR,
149 PointDataColumnType::ColorGreen => COLUMN_NAME_COLOR_GREEN_STR,
150 PointDataColumnType::ColorBlue => COLUMN_NAME_COLOR_BLUE_STR,
151 PointDataColumnType::SphericalAzimuth => COLUMN_NAME_SPHERICAL_AZIMUTH_STR,
152 PointDataColumnType::SphericalElevation => COLUMN_NAME_SPHERICAL_ELEVATION_STR,
153 PointDataColumnType::SphericalRange => COLUMN_NAME_SPHERICAL_RANGE_STR,
154 PointDataColumnType::OctantIndexLevel => COLUMN_NAME_OCTANT_INDEX_LEVEL_STR,
155 PointDataColumnType::OctantIndexX => COLUMN_NAME_OCTANT_INDEX_X_STR,
156 PointDataColumnType::OctantIndexY => COLUMN_NAME_OCTANT_INDEX_Y_STR,
157 PointDataColumnType::OctantIndexZ => COLUMN_NAME_OCTANT_INDEX_Z_STR,
158 PointDataColumnType::PointSourceId => COLUMN_NAME_POINT_SOURCE_ID_STR,
159 }
160 }
161
162 pub fn data_frame_data_type(&self) -> DataType {
163 match self {
164 PointDataColumnType::X => DataType::Float64,
165 PointDataColumnType::Y => DataType::Float64,
166 PointDataColumnType::Z => DataType::Float64,
167 PointDataColumnType::Id => DataType::UInt64,
168 PointDataColumnType::FrameId => DataType::Categorical(
169 Categories::new(
170 "frame_ids".into(),
171 "ecoord_frames".into(),
172 CategoricalPhysical::U8,
173 ),
174 Arc::new(CategoricalMapping::new(u8::MAX as usize)),
175 ),
176 PointDataColumnType::TimestampSecond => DataType::Int64,
177 PointDataColumnType::TimestampNanoSecond => DataType::UInt32,
178 PointDataColumnType::Intensity => DataType::Float32,
179 PointDataColumnType::SensorTranslationX => DataType::Float64,
180 PointDataColumnType::SensorTranslationY => DataType::Float64,
181 PointDataColumnType::SensorTranslationZ => DataType::Float64,
182 PointDataColumnType::SensorRotationX => DataType::Float64,
183 PointDataColumnType::SensorRotationY => DataType::Float64,
184 PointDataColumnType::SensorRotationZ => DataType::Float64,
185 PointDataColumnType::SensorRotationW => DataType::Float64,
186 PointDataColumnType::ColorRed => DataType::UInt16,
187 PointDataColumnType::ColorGreen => DataType::UInt16,
188 PointDataColumnType::ColorBlue => DataType::UInt16,
189 PointDataColumnType::SphericalAzimuth => DataType::Float64,
190 PointDataColumnType::SphericalElevation => DataType::Float64,
191 PointDataColumnType::SphericalRange => DataType::Float64,
192 PointDataColumnType::OctantIndexLevel => DataType::UInt32,
193 PointDataColumnType::OctantIndexX => DataType::UInt64,
194 PointDataColumnType::OctantIndexY => DataType::UInt64,
195 PointDataColumnType::OctantIndexZ => DataType::UInt64,
196 PointDataColumnType::PointSourceId => DataType::UInt16,
197 }
198 }
199}
200
201impl From<PointDataColumnType> for PlSmallStr {
202 fn from(value: PointDataColumnType) -> Self {
203 value.as_str().into()
204 }
205}
206
207#[derive(Debug, Clone, PartialEq)]
209pub struct PointData {
210 pub data_frame: DataFrame,
211}
212
213impl PointData {
214 pub fn new(data_frame: DataFrame) -> Result<Self, Error> {
215 if data_frame.is_empty() {
216 return Err(NoData("point_data"));
217 }
218
219 let data_frame_column_types: Vec<PointDataColumnType> = data_frame
221 .get_column_names()
222 .iter()
223 .filter_map(|x| PointDataColumnType::from_str(x).ok())
224 .collect();
225
226 for current_column_type in data_frame_column_types {
227 let current_column = data_frame
228 .column(current_column_type.as_str())
229 .expect("column must exist");
230 if current_column.dtype() != ¤t_column_type.data_frame_data_type() {
231 return Err(TypeMismatch {
232 column: current_column_type.as_str(),
233 expected: current_column_type.data_frame_data_type().to_string(),
234 actual: current_column.dtype().to_string(),
235 });
236 }
237 }
238
239 Ok(Self { data_frame })
240 }
241
242 pub fn new_unchecked(data_frame: DataFrame) -> Self {
243 Self { data_frame }
244 }
245
246 pub fn height(&self) -> usize {
247 self.data_frame.height()
248 }
249
250 pub fn is_empty(&self) -> bool {
251 self.data_frame.is_empty()
252 }
253}
254
255impl PointData {
256 pub fn get_x_values(&self) -> &Float64Chunked {
257 self.data_frame
258 .column(PointDataColumnType::X.as_str())
259 .expect("column mandatory")
260 .f64()
261 .expect("type must be f64")
262 }
263 pub fn get_y_values(&self) -> &Float64Chunked {
264 self.data_frame
265 .column(PointDataColumnType::Y.as_str())
266 .expect("column mandatory")
267 .f64()
268 .expect("type must be f64")
269 }
270 pub fn get_z_values(&self) -> &Float64Chunked {
271 self.data_frame
272 .column(PointDataColumnType::Z.as_str())
273 .expect("column mandatory")
274 .f64()
275 .expect("type must be f64")
276 }
277
278 pub fn get_id_values(&self) -> Result<&UInt64Chunked, Error> {
279 let values = self
280 .data_frame
281 .column(PointDataColumnType::Id.as_str())?
282 .u64()
283 .expect("type must be u64");
284 Ok(values)
285 }
286
287 pub fn get_frame_id_values(&self) -> Result<&Categorical8Chunked, Error> {
288 let values = self
289 .data_frame
290 .column(PointDataColumnType::FrameId.as_str())?
291 .cat8()
292 .expect("type must be categorical");
293 Ok(values)
294 }
295
296 pub fn get_timestamp_sec_values(&self) -> Result<&Int64Chunked, Error> {
297 let values = self
298 .data_frame
299 .column(PointDataColumnType::TimestampSecond.as_str())?
300 .i64()
301 .expect("type must be i64");
302 Ok(values)
303 }
304
305 pub fn get_timestamp_nanosec_values(&self) -> Result<&UInt32Chunked, Error> {
306 let values = self
307 .data_frame
308 .column(PointDataColumnType::TimestampNanoSecond.as_str())?
309 .u32()
310 .expect("type must be u32");
311 Ok(values)
312 }
313
314 pub fn get_intensity_values(&self) -> Result<&Float32Chunked, Error> {
315 let values = self
316 .data_frame
317 .column(PointDataColumnType::Intensity.as_str())?
318 .f32()
319 .expect("type must be f32");
320 Ok(values)
321 }
322
323 pub fn get_sensor_translation_x_values(&self) -> Result<&Float64Chunked, Error> {
324 let values = self
325 .data_frame
326 .column(PointDataColumnType::SensorTranslationX.as_str())?
327 .f64()
328 .expect("type must be f64");
329 Ok(values)
330 }
331
332 pub fn get_sensor_translation_y_values(&self) -> Result<&Float64Chunked, Error> {
333 let values = self
334 .data_frame
335 .column(PointDataColumnType::SensorTranslationY.as_str())?
336 .f64()
337 .expect("type must be f64");
338 Ok(values)
339 }
340
341 pub fn get_sensor_translation_z_values(&self) -> Result<&Float64Chunked, Error> {
342 let values = self
343 .data_frame
344 .column(PointDataColumnType::SensorTranslationZ.as_str())?
345 .f64()
346 .expect("type must be f64");
347 Ok(values)
348 }
349
350 pub fn get_sensor_rotation_x_values(&self) -> Result<&Float64Chunked, Error> {
351 let values = self
352 .data_frame
353 .column(PointDataColumnType::SensorRotationX.as_str())?
354 .f64()
355 .expect("type must be f64");
356 Ok(values)
357 }
358
359 pub fn get_sensor_rotation_y_values(&self) -> Result<&Float64Chunked, Error> {
360 let values = self
361 .data_frame
362 .column(PointDataColumnType::SensorRotationY.as_str())?
363 .f64()
364 .expect("type must be f64");
365 Ok(values)
366 }
367
368 pub fn get_sensor_rotation_z_values(&self) -> Result<&Float64Chunked, Error> {
369 let values = self
370 .data_frame
371 .column(PointDataColumnType::SensorRotationZ.as_str())?
372 .f64()
373 .expect("type must be f64");
374 Ok(values)
375 }
376
377 pub fn get_sensor_rotation_w_values(&self) -> Result<&Float64Chunked, Error> {
378 let values = self
379 .data_frame
380 .column(PointDataColumnType::SensorRotationW.as_str())?
381 .f64()
382 .expect("type must be f64");
383 Ok(values)
384 }
385
386 pub fn get_color_red_values(&self) -> Result<&UInt16Chunked, Error> {
387 let values = self
388 .data_frame
389 .column(PointDataColumnType::ColorRed.as_str())?
390 .u16()
391 .expect("type must be u16");
392 Ok(values)
393 }
394
395 pub fn get_color_green_values(&self) -> Result<&UInt16Chunked, Error> {
396 let values = self
397 .data_frame
398 .column(PointDataColumnType::ColorGreen.as_str())?
399 .u16()
400 .expect("type must be u16");
401 Ok(values)
402 }
403
404 pub fn get_color_blue_values(&self) -> Result<&UInt16Chunked, Error> {
405 let values = self
406 .data_frame
407 .column(PointDataColumnType::ColorBlue.as_str())?
408 .u16()
409 .expect("type must be u16");
410 Ok(values)
411 }
412
413 pub fn get_spherical_azimuth_values(&self) -> Result<&Float64Chunked, Error> {
414 let values = self
415 .data_frame
416 .column(PointDataColumnType::SphericalAzimuth.as_str())?
417 .f64()
418 .expect("type must be f64");
419 Ok(values)
420 }
421
422 pub fn get_spherical_elevation_values(&self) -> Result<&Float64Chunked, Error> {
423 let values = self
424 .data_frame
425 .column(PointDataColumnType::SphericalElevation.as_str())?
426 .f64()
427 .expect("type must be f64");
428 Ok(values)
429 }
430
431 pub fn get_spherical_range_values(&self) -> Result<&Float64Chunked, Error> {
432 let values = self
433 .data_frame
434 .column(PointDataColumnType::SphericalRange.as_str())?
435 .f64()
436 .expect("type must be f64");
437 Ok(values)
438 }
439
440 pub fn get_point_source_id_values(&self) -> Result<&UInt16Chunked, Error> {
441 let values = self
442 .data_frame
443 .column(PointDataColumnType::PointSourceId.as_str())?
444 .u16()
445 .expect("type must be f64");
446 Ok(values)
447 }
448}
449
450impl PointData {
451 pub fn contains_id_column(&self) -> bool {
452 self.data_frame
453 .column(PointDataColumnType::Id.as_str())
454 .is_ok()
455 }
456
457 pub fn contains_frame_id_column(&self) -> bool {
458 self.data_frame
459 .column(PointDataColumnType::FrameId.as_str())
460 .is_ok()
461 }
462
463 pub fn contains_timestamp_sec_column(&self) -> bool {
464 self.data_frame
465 .column(PointDataColumnType::TimestampSecond.as_str())
466 .is_ok()
467 }
468
469 pub fn contains_timestamp_nanosec_column(&self) -> bool {
470 self.data_frame
471 .column(PointDataColumnType::TimestampNanoSecond.as_str())
472 .is_ok()
473 }
474
475 pub fn contains_intensity_column(&self) -> bool {
476 self.data_frame
477 .column(PointDataColumnType::Intensity.as_str())
478 .is_ok()
479 }
480
481 pub fn contains_sensor_translation_x_column(&self) -> bool {
482 self.data_frame
483 .column(PointDataColumnType::SensorTranslationX.as_str())
484 .is_ok()
485 }
486
487 pub fn contains_sensor_translation_y_column(&self) -> bool {
488 self.data_frame
489 .column(PointDataColumnType::SensorTranslationY.as_str())
490 .is_ok()
491 }
492
493 pub fn contains_sensor_translation_z_column(&self) -> bool {
494 self.data_frame
495 .column(PointDataColumnType::SensorTranslationZ.as_str())
496 .is_ok()
497 }
498
499 pub fn contains_sensor_rotation_x_column(&self) -> bool {
500 self.data_frame
501 .column(PointDataColumnType::SensorRotationX.as_str())
502 .is_ok()
503 }
504
505 pub fn contains_sensor_rotation_y_column(&self) -> bool {
506 self.data_frame
507 .column(PointDataColumnType::SensorRotationY.as_str())
508 .is_ok()
509 }
510
511 pub fn contains_sensor_rotation_z_column(&self) -> bool {
512 self.data_frame
513 .column(PointDataColumnType::SensorRotationZ.as_str())
514 .is_ok()
515 }
516
517 pub fn contains_sensor_rotation_w_column(&self) -> bool {
518 self.data_frame
519 .column(PointDataColumnType::SensorRotationW.as_str())
520 .is_ok()
521 }
522
523 pub fn contains_color_red_column(&self) -> bool {
524 self.data_frame
525 .column(PointDataColumnType::ColorRed.as_str())
526 .is_ok()
527 }
528
529 pub fn contains_color_green_column(&self) -> bool {
530 self.data_frame
531 .column(PointDataColumnType::ColorGreen.as_str())
532 .is_ok()
533 }
534
535 pub fn contains_color_blue_column(&self) -> bool {
536 self.data_frame
537 .column(PointDataColumnType::ColorBlue.as_str())
538 .is_ok()
539 }
540
541 pub fn contains_spherical_azimuth_column(&self) -> bool {
542 self.data_frame
543 .column(PointDataColumnType::SphericalAzimuth.as_str())
544 .is_ok()
545 }
546
547 pub fn contains_spherical_elevation_column(&self) -> bool {
548 self.data_frame
549 .column(PointDataColumnType::SphericalElevation.as_str())
550 .is_ok()
551 }
552
553 pub fn contains_spherical_range_column(&self) -> bool {
554 self.data_frame
555 .column(PointDataColumnType::SphericalRange.as_str())
556 .is_ok()
557 }
558
559 pub fn contains_point_source_id_column(&self) -> bool {
560 self.data_frame
561 .column(PointDataColumnType::PointSourceId.as_str())
562 .is_ok()
563 }
564
565 pub fn contains_octant_index_level_column(&self) -> bool {
566 self.data_frame
567 .column(PointDataColumnType::OctantIndexLevel.as_str())
568 .is_ok()
569 }
570
571 pub fn contains_octant_index_x_column(&self) -> bool {
572 self.data_frame
573 .column(PointDataColumnType::OctantIndexX.as_str())
574 .is_ok()
575 }
576
577 pub fn contains_octant_index_y_column(&self) -> bool {
578 self.data_frame
579 .column(PointDataColumnType::OctantIndexY.as_str())
580 .is_ok()
581 }
582
583 pub fn contains_octant_index_z_column(&self) -> bool {
584 self.data_frame
585 .column(PointDataColumnType::OctantIndexZ.as_str())
586 .is_ok()
587 }
588}
589
590impl PointData {
591 pub fn contains_timestamps(&self) -> bool {
592 self.contains_timestamp_sec_column() && self.contains_timestamp_nanosec_column()
593 }
594
595 pub fn contains_sensor_translation(&self) -> bool {
596 self.contains_sensor_translation_x_column()
597 && self.contains_sensor_translation_y_column()
598 && self.contains_sensor_translation_z_column()
599 }
600
601 pub fn contains_sensor_rotation(&self) -> bool {
602 self.contains_sensor_rotation_x_column()
603 && self.contains_sensor_rotation_y_column()
604 && self.contains_sensor_rotation_z_column()
605 && self.contains_sensor_rotation_w_column()
606 }
607
608 pub fn contains_sensor_pose(&self) -> bool {
609 self.contains_sensor_translation() && self.contains_sensor_rotation()
610 }
611
612 pub fn contains_colors(&self) -> bool {
613 self.contains_color_red_column()
614 && self.contains_color_green_column()
615 && self.contains_color_blue_column()
616 }
617
618 pub fn contains_octant_indices(&self) -> bool {
619 self.contains_octant_index_level_column()
620 && self.contains_octant_index_x_column()
621 && self.contains_octant_index_y_column()
622 && self.contains_octant_index_z_column()
623 }
624}
625
626impl PointData {
627 pub fn get_all_points(&self) -> Vec<Point3<f64>> {
629 let x_values = self.get_x_values();
630 let y_values = self.get_y_values();
631 let z_values = self.get_z_values();
632
633 let all_points: Vec<Point3<f64>> = (0..self.data_frame.height())
634 .into_par_iter()
635 .map(|i: usize| {
636 Point3::new(
637 x_values.get(i).unwrap(),
638 y_values.get(i).unwrap(),
639 z_values.get(i).unwrap(),
640 )
641 })
642 .collect();
643
644 all_points
645 }
646
647 pub fn get_all_frame_ids(&self) -> Result<Vec<FrameId>, Error> {
648 let values = self
649 .get_frame_id_values()?
650 .cast(&DataType::String)?
651 .str()?
652 .into_no_null_iter()
653 .map(|f| f.to_string().into())
654 .collect();
655 Ok(values)
656 }
657
658 pub fn get_all_timestamps(&self) -> Result<Vec<DateTime<Utc>>, Error> {
659 let timestamp_sec_series = self.get_timestamp_sec_values()?;
660 let timestamp_nanosec_series = self.get_timestamp_nanosec_values()?;
661
662 let timestamps: Vec<DateTime<Utc>> = timestamp_sec_series
663 .into_iter()
664 .zip(timestamp_nanosec_series)
665 .map(|(current_sec, current_nanosec)| {
666 Utc.timestamp_opt(current_sec.unwrap(), current_nanosec.unwrap())
667 .unwrap()
668 })
669 .collect();
670 Ok(timestamps)
671 }
672
673 pub fn get_all_sensor_translations(&self) -> Result<Vec<Point3<f64>>, Error> {
675 let x_values = self.get_sensor_translation_x_values()?;
676 let y_values = self.get_sensor_translation_y_values()?;
677 let z_values = self.get_sensor_translation_z_values()?;
678
679 let all_sensor_translations: Vec<Point3<f64>> = (0..self.data_frame.height())
680 .into_par_iter()
681 .map(|i: usize| {
682 Point3::new(
683 x_values.get(i).unwrap(),
684 y_values.get(i).unwrap(),
685 z_values.get(i).unwrap(),
686 )
687 })
688 .collect();
689
690 Ok(all_sensor_translations)
691 }
692
693 pub fn get_all_sensor_rotations(&self) -> Result<Vec<UnitQuaternion<f64>>, Error> {
695 let i_values = self.get_sensor_rotation_x_values()?;
696 let j_values = self.get_sensor_rotation_y_values()?;
697 let k_values = self.get_sensor_rotation_z_values()?;
698 let w_values = self.get_sensor_rotation_w_values()?;
699
700 let all_sensor_rotations: Vec<UnitQuaternion<f64>> = (0..self.data_frame.height())
701 .into_par_iter()
702 .map(|i: usize| {
703 UnitQuaternion::new_unchecked(Quaternion::new(
704 w_values.get(i).unwrap(),
705 i_values.get(i).unwrap(),
706 j_values.get(i).unwrap(),
707 k_values.get(i).unwrap(),
708 ))
709 })
710 .collect();
711
712 Ok(all_sensor_rotations)
713 }
714
715 pub fn get_all_sensor_poses(&self) -> Result<Vec<Isometry3<f64>>, Error> {
717 let sensor_translations = self.get_all_sensor_translations()?;
718 let sensor_rotations = self.get_all_sensor_rotations()?;
719
720 let all_sensor_poses: Vec<Isometry3<f64>> = (0..self.data_frame.height())
721 .into_par_iter()
722 .map(|i: usize| {
723 Isometry3::from_parts(
724 (*sensor_translations.get(i).unwrap()).into(),
725 *sensor_rotations.get(i).unwrap(),
726 )
727 })
728 .collect();
729
730 Ok(all_sensor_poses)
731 }
732
733 pub fn get_all_colors(&self) -> Result<Vec<Srgb<u16>>, Error> {
734 let red_color_values = self.get_color_red_values()?;
735 let green_color_values = self.get_color_green_values()?;
736 let blue_color_values = self.get_color_blue_values()?;
737
738 let all_colors: Vec<Srgb<u16>> = (0..self.data_frame.height())
739 .into_par_iter()
740 .map(|i: usize| {
741 Srgb::new(
742 red_color_values.get(i).unwrap(),
743 green_color_values.get(i).unwrap(),
744 blue_color_values.get(i).unwrap(),
745 )
746 })
747 .collect();
748
749 Ok(all_colors)
750 }
751
752 pub fn get_all_spherical_points(&self) -> Result<Vec<SphericalPoint3<f64>>, Error> {
753 let range_values = self.get_spherical_range_values()?;
754 let elevation_values = self.get_spherical_elevation_values()?;
755 let azimuth_values = self.get_spherical_azimuth_values()?;
756
757 let all_spherical_points: Vec<SphericalPoint3<f64>> = (0..self.data_frame.height())
758 .into_par_iter()
759 .map(|i: usize| {
760 SphericalPoint3::new(
761 range_values.get(i).unwrap(),
762 elevation_values.get(i).unwrap(),
763 azimuth_values.get(i).unwrap(),
764 )
765 })
766 .collect();
767
768 Ok(all_spherical_points)
769 }
770}
771
772impl PointData {
773 pub fn remove_colors(&mut self) -> Result<(), Error> {
774 self.data_frame = self.data_frame.drop_many([
775 PointDataColumnType::ColorRed.as_str(),
776 PointDataColumnType::ColorGreen.as_str(),
777 PointDataColumnType::ColorBlue.as_str(),
778 ]);
779
780 Ok(())
781 }
782}
783
784impl PointData {
785 pub fn get_distinct_frame_ids(&self) -> Result<HashSet<FrameId>, Error> {
786 let values: HashSet<FrameId> = self
787 .data_frame
788 .column(PointDataColumnType::FrameId.as_str())?
789 .unique()?
790 .cat8()
791 .expect("type must be categorical")
792 .cast(&DataType::String)
793 .unwrap()
794 .str()
795 .unwrap()
796 .into_no_null_iter()
797 .map(|f| f.to_string().into())
798 .collect();
799
800 Ok(values)
801 }
802
803 pub fn get_timestamp_min(&self) -> Result<Option<DateTime<Utc>>, Error> {
804 if !self.contains_timestamps() {
805 return Ok(None);
806 }
807
808 let all_timestamps = self.get_all_timestamps()?;
809 let value = all_timestamps.iter().min();
810 Ok(value.copied())
811 }
812
813 pub fn get_timestamp_max(&self) -> Result<Option<DateTime<Utc>>, Error> {
814 if !self.contains_timestamps() {
815 return Ok(None);
816 }
817
818 let all_timestamps = self.get_all_timestamps()?;
819 let value = all_timestamps.iter().max();
820 Ok(value.copied())
821 }
822
823 pub fn get_median_time(&self) -> Result<DateTime<Utc>, Error> {
824 let mut all_time = self.get_all_timestamps()?;
825 all_time.sort();
826 let mid = all_time.len() / 2;
827 Ok(all_time[mid])
828 }
829
830 pub fn get_axis_aligned_bounding_box(&self) -> AxisAlignedBoundingBox {
832 let min_bound = self.get_local_min();
833 let max_bound = self.get_local_max();
834
835 AxisAlignedBoundingBox::new(min_bound, max_bound).expect("should work")
836 }
837
838 pub fn get_local_min(&self) -> Point3<f64> {
840 let x = self.get_x_values().min().expect("point cloud not empty");
841 let y = self.get_y_values().min().expect("point cloud not empty");
842 let z = self.get_z_values().min().expect("point cloud not empty");
843 Point3::new(x, y, z)
844 }
845
846 pub fn get_local_max(&self) -> Point3<f64> {
848 let x = self.get_x_values().max().expect("point cloud not empty");
849 let y = self.get_y_values().max().expect("point cloud not empty");
850 let z = self.get_z_values().max().expect("point cloud not empty");
851 Point3::new(x, y, z)
852 }
853
854 pub fn get_local_center(&self) -> Point3<f64> {
856 let local_min = self.get_local_min();
857 let diagonal = self.get_local_max() - local_min;
858 local_min + diagonal / 2.0
859 }
860
861 pub fn get_local_sensor_translation_min(&self) -> Result<Point3<f64>, Error> {
863 let x = self
864 .get_sensor_translation_x_values()?
865 .min()
866 .expect("point cloud not empty");
867 let y = self
868 .get_sensor_translation_y_values()?
869 .min()
870 .expect("point cloud not empty");
871 let z = self
872 .get_sensor_translation_z_values()?
873 .min()
874 .expect("point cloud not empty");
875 Ok(Point3::new(x, y, z))
876 }
877
878 pub fn get_local_sensor_translation_max(&self) -> Result<Point3<f64>, Error> {
880 let x = self
881 .get_sensor_translation_x_values()?
882 .max()
883 .expect("point cloud not empty");
884 let y = self
885 .get_sensor_translation_y_values()?
886 .max()
887 .expect("point cloud not empty");
888 let z = self
889 .get_sensor_translation_z_values()?
890 .max()
891 .expect("point cloud not empty");
892 Ok(Point3::new(x, y, z))
893 }
894
895 pub fn get_id_min(&self) -> Result<Option<u64>, Error> {
896 let value = self.get_id_values()?.min();
897 Ok(value)
898 }
899
900 pub fn get_id_max(&self) -> Result<Option<u64>, Error> {
901 let value = self.get_id_values()?.max();
902 Ok(value)
903 }
904
905 pub fn get_intensity_min(&self) -> Result<Option<f32>, Error> {
906 let value = self.get_intensity_values()?.min();
907 Ok(value)
908 }
909 pub fn get_intensity_max(&self) -> Result<Option<f32>, Error> {
910 let value = self.get_intensity_values()?.max();
911 Ok(value)
912 }
913
914 pub fn derive_convex_hull(&self) -> Option<ConvexPolyhedron> {
915 let points: Vec<parry3d_f64::math::Point<f64>> = self
916 .get_all_points()
917 .iter()
918 .map(|p| parry3d_f64::math::Point::new(p.x, p.y, p.z))
919 .collect();
920 ConvexPolyhedron::from_convex_hull(&points)
921 }
922}
923
924impl PointData {
925 pub fn add_sequential_id(&mut self) -> Result<(), Error> {
927 if self.contains_id_column() {
928 return Err(ColumnAlreadyExists(PointDataColumnType::Id.as_str()));
929 }
930
931 let values: Vec<u64> = Vec::from_iter(0u64..self.data_frame.height() as u64);
932 if values.len() != self.data_frame.height() {
933 return Err(ShapeMismatch("should have the same height"));
934 }
935
936 let new_series = Series::new(PointDataColumnType::Id.into(), values);
937 self.data_frame.with_column(new_series)?;
938
939 Ok(())
940 }
941
942 pub fn derive_spherical_points(&mut self) -> Result<(), Error> {
944 let spherical_points: Vec<SphericalPoint3<f64>> = self
945 .get_all_points()
946 .into_par_iter()
947 .map(|p| p.into())
948 .collect();
949
950 self.add_spherical_points(spherical_points)?;
951
952 Ok(())
953 }
954
955 pub fn add_i64_column(&mut self, name: &str, values: Vec<i64>) -> Result<(), Error> {
957 if values.len() != self.data_frame.height() {
958 return Err(ShapeMismatch(
959 "values have a different length than point_data",
960 ));
961 }
962
963 let new_series = Series::new(name.into(), values);
964 self.data_frame.with_column(new_series)?;
965 Ok(())
966 }
967
968 pub fn add_u16_column(&mut self, name: &str, values: Vec<u16>) -> Result<(), Error> {
970 if values.len() != self.data_frame.height() {
971 return Err(ShapeMismatch(
972 "values have a different length than point_data",
973 ));
974 }
975
976 let new_series = Series::new(name.into(), values);
977 self.data_frame.with_column(new_series)?;
978 Ok(())
979 }
980
981 pub fn add_u32_column(&mut self, name: &str, values: Vec<u32>) -> Result<(), Error> {
983 if values.len() != self.data_frame.height() {
984 return Err(ShapeMismatch(
985 "values have a different length than point_data",
986 ));
987 }
988
989 let new_series = Series::new(name.into(), values);
990 self.data_frame.with_column(new_series)?;
991 Ok(())
992 }
993
994 pub fn add_u64_column(&mut self, name: &str, values: Vec<u64>) -> Result<(), Error> {
996 if values.len() != self.data_frame.height() {
997 return Err(ShapeMismatch(
998 "values have a different length than point_data",
999 ));
1000 }
1001
1002 let new_series = Series::new(name.into(), values);
1003 self.data_frame.with_column(new_series)?;
1004 Ok(())
1005 }
1006
1007 pub fn add_f32_column(&mut self, name: &str, values: Vec<f32>) -> Result<(), Error> {
1009 if values.len() != self.data_frame.height() {
1010 return Err(ShapeMismatch(
1011 "values have a different length than point_data",
1012 ));
1013 }
1014
1015 let new_series = Series::new(name.into(), values);
1016 self.data_frame.with_column(new_series)?;
1017 Ok(())
1018 }
1019
1020 pub fn add_f64_column(&mut self, name: &str, values: Vec<f64>) -> Result<(), Error> {
1022 if values.len() != self.data_frame.height() {
1023 return Err(ShapeMismatch(
1024 "values have a different length than point_data",
1025 ));
1026 }
1027
1028 let new_series = Series::new(name.into(), values);
1029 self.data_frame.with_column(new_series)?;
1030 Ok(())
1031 }
1032
1033 pub fn remove_column(&mut self, column: &str) -> Result<(), Error> {
1035 if column == PointDataColumnType::X.as_str()
1036 || column == PointDataColumnType::Y.as_str()
1037 || column == PointDataColumnType::Z.as_str()
1038 {
1039 return Err(ObligatoryColumn);
1040 }
1041 self.data_frame = self.data_frame.drop(column)?;
1042
1043 Ok(())
1044 }
1045}
1046
1047impl PointData {
1048 pub fn update_points_in_place(&mut self, points: Vec<Point3<f64>>) -> Result<(), Error> {
1049 if points.len() != self.data_frame.height() {
1050 return Err(ShapeMismatch("points"));
1051 }
1052
1053 if self
1054 .data_frame
1055 .column(PointDataColumnType::FrameId.as_str())
1056 .is_ok()
1057 {
1058 let _ = self
1059 .data_frame
1060 .drop_in_place(PointDataColumnType::FrameId.as_str())
1061 .expect("Column should be successfully replaced");
1062 }
1063
1064 let x_series = Series::new(
1065 PointDataColumnType::X.into(),
1066 points.iter().map(|p| p.x).collect::<Vec<f64>>(),
1067 );
1068 let y_series = Series::new(
1069 PointDataColumnType::Y.into(),
1070 points.iter().map(|p| p.y).collect::<Vec<f64>>(),
1071 );
1072 let z_series = Series::new(
1073 PointDataColumnType::Z.into(),
1074 points.iter().map(|p| p.z).collect::<Vec<f64>>(),
1075 );
1076 self.data_frame
1077 .replace(PointDataColumnType::X.as_str(), x_series)?;
1078 self.data_frame
1079 .replace(PointDataColumnType::Y.as_str(), y_series)?;
1080 self.data_frame
1081 .replace(PointDataColumnType::Z.as_str(), z_series)?;
1082
1083 Ok(())
1084 }
1085
1086 pub fn update_sensor_translations_in_place(
1089 &mut self,
1090 sensor_translations: Vec<Point3<f64>>,
1091 ) -> Result<(), Error> {
1092 if sensor_translations.len() != self.data_frame.height() {
1093 return Err(ShapeMismatch(
1094 "sensor_translations has a different size than the point_data",
1095 ));
1096 }
1097
1098 let sensor_translation_x_series = Series::new(
1099 PointDataColumnType::SensorTranslationX.into(),
1100 sensor_translations
1101 .iter()
1102 .map(|p| p.x)
1103 .collect::<Vec<f64>>(),
1104 );
1105 let sensor_translation_y_series = Series::new(
1106 PointDataColumnType::SensorTranslationY.into(),
1107 sensor_translations
1108 .iter()
1109 .map(|p| p.y)
1110 .collect::<Vec<f64>>(),
1111 );
1112 let sensor_translation_z_series = Series::new(
1113 PointDataColumnType::SensorTranslationZ.into(),
1114 sensor_translations
1115 .iter()
1116 .map(|p| p.z)
1117 .collect::<Vec<f64>>(),
1118 );
1119 self.data_frame.replace(
1120 PointDataColumnType::SensorTranslationX.as_str(),
1121 sensor_translation_x_series,
1122 )?;
1123 self.data_frame.replace(
1124 PointDataColumnType::SensorTranslationY.as_str(),
1125 sensor_translation_y_series,
1126 )?;
1127 self.data_frame.replace(
1128 PointDataColumnType::SensorTranslationZ.as_str(),
1129 sensor_translation_z_series,
1130 )?;
1131
1132 Ok(())
1133 }
1134
1135 pub fn update_sensor_rotations_in_place(
1136 &mut self,
1137 sensor_rotations: Vec<UnitQuaternion<f64>>,
1138 ) -> Result<(), Error> {
1139 if sensor_rotations.len() != self.data_frame.height() {
1140 return Err(ShapeMismatch(
1141 "sensor_translations has a different size than the point_data",
1142 ));
1143 }
1144
1145 let sensor_rotation_x_series = Series::new(
1146 PointDataColumnType::SensorRotationX.into(),
1147 sensor_rotations.iter().map(|r| r.i).collect::<Vec<f64>>(),
1148 );
1149 let sensor_rotation_y_series = Series::new(
1150 PointDataColumnType::SensorRotationY.into(),
1151 sensor_rotations.iter().map(|r| r.j).collect::<Vec<f64>>(),
1152 );
1153 let sensor_rotation_z_series = Series::new(
1154 PointDataColumnType::SensorRotationZ.into(),
1155 sensor_rotations.iter().map(|r| r.k).collect::<Vec<f64>>(),
1156 );
1157 let sensor_rotation_w_series = Series::new(
1158 PointDataColumnType::SensorRotationW.into(),
1159 sensor_rotations.iter().map(|r| r.w).collect::<Vec<f64>>(),
1160 );
1161
1162 self.data_frame.replace(
1163 PointDataColumnType::SensorRotationX.as_str(),
1164 sensor_rotation_x_series,
1165 )?;
1166 self.data_frame.replace(
1167 PointDataColumnType::SensorRotationY.as_str(),
1168 sensor_rotation_y_series,
1169 )?;
1170 self.data_frame.replace(
1171 PointDataColumnType::SensorRotationZ.as_str(),
1172 sensor_rotation_z_series,
1173 )?;
1174 self.data_frame.replace(
1175 PointDataColumnType::SensorRotationW.as_str(),
1176 sensor_rotation_w_series,
1177 )?;
1178
1179 Ok(())
1180 }
1181
1182 pub fn update_sensor_poses_in_place(
1183 &mut self,
1184 sensor_poses: Vec<Isometry3<f64>>,
1185 ) -> Result<(), Error> {
1186 if sensor_poses.len() != self.data_frame.height() {
1187 return Err(ShapeMismatch(
1188 "sensor_poses has a different size than the point_data",
1189 ));
1190 }
1191
1192 let sensor_translations: Vec<Point3<f64>> = sensor_poses
1193 .iter()
1194 .map(|i| i.translation.vector.into())
1195 .collect();
1196 self.update_sensor_translations_in_place(sensor_translations)?;
1197
1198 let sensor_rotations: Vec<UnitQuaternion<f64>> =
1199 sensor_poses.into_iter().map(|i| i.rotation).collect();
1200 self.update_sensor_rotations_in_place(sensor_rotations)?;
1201
1202 Ok(())
1203 }
1204
1205 pub fn add_spherical_points(
1206 &mut self,
1207 spherical_points: Vec<SphericalPoint3<f64>>,
1208 ) -> Result<(), Error> {
1209 if spherical_points.len() != self.data_frame.height() {
1210 return Err(ShapeMismatch(
1211 "spherical_points has a different size than the point_data",
1212 ));
1213 }
1214
1215 let spherical_azimuth_series = Series::new(
1216 PointDataColumnType::SphericalAzimuth.into(),
1217 spherical_points.iter().map(|p| p.phi).collect::<Vec<f64>>(),
1218 );
1219 let spherical_elevation_series = Series::new(
1220 PointDataColumnType::SphericalElevation.into(),
1221 spherical_points
1222 .iter()
1223 .map(|p| p.theta)
1224 .collect::<Vec<f64>>(),
1225 );
1226 let spherical_range_series = Series::new(
1227 PointDataColumnType::SphericalRange.into(),
1228 spherical_points.iter().map(|p| p.r).collect::<Vec<f64>>(),
1229 );
1230 self.data_frame.with_column(spherical_azimuth_series)?;
1231 self.data_frame.with_column(spherical_elevation_series)?;
1232 self.data_frame.with_column(spherical_range_series)?;
1233
1234 Ok(())
1235 }
1236
1237 pub fn add_octant_indices(&mut self, octant_indices: Vec<OctantIndex>) -> Result<(), Error> {
1238 if octant_indices.len() != self.data_frame.height() {
1239 return Err(ShapeMismatch(
1240 "octant_indices has a different size than the point_data",
1241 ));
1242 }
1243
1244 let octant_index_level_series = Series::new(
1245 PointDataColumnType::OctantIndexLevel.into(),
1246 octant_indices.iter().map(|i| i.level).collect::<Vec<u32>>(),
1247 );
1248 let octant_index_x_series = Series::new(
1249 PointDataColumnType::OctantIndexX.into(),
1250 octant_indices.iter().map(|i| i.x).collect::<Vec<u64>>(),
1251 );
1252 let octant_index_y_series = Series::new(
1253 PointDataColumnType::OctantIndexY.into(),
1254 octant_indices.iter().map(|i| i.y).collect::<Vec<u64>>(),
1255 );
1256 let octant_index_z_series = Series::new(
1257 PointDataColumnType::OctantIndexZ.into(),
1258 octant_indices.iter().map(|i| i.z).collect::<Vec<u64>>(),
1259 );
1260
1261 self.data_frame.with_column(octant_index_level_series)?;
1262 self.data_frame.with_column(octant_index_x_series)?;
1263 self.data_frame.with_column(octant_index_y_series)?;
1264 self.data_frame.with_column(octant_index_z_series)?;
1265
1266 Ok(())
1267 }
1268
1269 pub fn add_unique_frame_id(&mut self, frame_id: FrameId) -> Result<(), Error> {
1270 let frame_ids = vec![frame_id; self.data_frame.height()];
1271 self.add_frame_ids(frame_ids)?;
1272
1273 Ok(())
1274 }
1275
1276 pub fn add_frame_ids(&mut self, frame_ids: Vec<FrameId>) -> Result<(), Error> {
1277 if self.contains_frame_id_column() {
1278 return Err(ColumnAlreadyExists(PointDataColumnType::FrameId.as_str()));
1279 };
1280 if frame_ids.len() != self.data_frame.height() {
1281 return Err(ShapeMismatch(
1282 "frame_ids has a different size than the point_data",
1283 ));
1284 }
1285
1286 let frame_id_series = Series::new(
1287 PointDataColumnType::FrameId.into(),
1288 frame_ids
1289 .into_iter()
1290 .map(|x| x.to_string())
1291 .collect::<Vec<String>>(),
1292 )
1293 .cast(&PointDataColumnType::FrameId.data_frame_data_type())
1294 .unwrap();
1295 self.data_frame.with_column(frame_id_series)?;
1296
1297 Ok(())
1298 }
1299
1300 pub fn add_unique_sensor_translation(
1301 &mut self,
1302 sensor_translation: Point3<f64>,
1303 ) -> Result<(), Error> {
1304 let sensor_translations: Vec<Point3<f64>> =
1305 vec![sensor_translation; self.data_frame.height()];
1306 self.add_sensor_translations(sensor_translations)?;
1307
1308 Ok(())
1309 }
1310
1311 pub fn add_sensor_translations(
1312 &mut self,
1313 sensor_translations: Vec<Point3<f64>>,
1314 ) -> Result<(), Error> {
1315 if sensor_translations.len() != self.data_frame.height() {
1316 return Err(ShapeMismatch(
1317 "sensor_translation has a different size than the point_data",
1318 ));
1319 }
1320
1321 let sensor_translation_x_series = Series::new(
1322 PointDataColumnType::SensorTranslationX.into(),
1323 sensor_translations
1324 .iter()
1325 .map(|p| p.x)
1326 .collect::<Vec<f64>>(),
1327 );
1328 let sensor_translation_y_series = Series::new(
1329 PointDataColumnType::SensorTranslationY.into(),
1330 sensor_translations
1331 .iter()
1332 .map(|p| p.y)
1333 .collect::<Vec<f64>>(),
1334 );
1335 let sensor_translation_z_series = Series::new(
1336 PointDataColumnType::SensorTranslationZ.into(),
1337 sensor_translations
1338 .iter()
1339 .map(|p| p.z)
1340 .collect::<Vec<f64>>(),
1341 );
1342 self.data_frame.with_column(sensor_translation_x_series)?;
1343 self.data_frame.with_column(sensor_translation_y_series)?;
1344 self.data_frame.with_column(sensor_translation_z_series)?;
1345
1346 Ok(())
1347 }
1348
1349 pub fn add_unique_sensor_rotation(
1350 &mut self,
1351 sensor_rotation: UnitQuaternion<f64>,
1352 ) -> Result<(), Error> {
1353 let sensor_rotations: Vec<UnitQuaternion<f64>> =
1354 vec![sensor_rotation; self.data_frame.height()];
1355 self.add_sensor_rotations(sensor_rotations)?;
1356
1357 Ok(())
1358 }
1359
1360 pub fn add_sensor_rotations(
1361 &mut self,
1362 sensor_rotations: Vec<UnitQuaternion<f64>>,
1363 ) -> Result<(), Error> {
1364 if sensor_rotations.len() != self.data_frame.height() {
1365 return Err(ShapeMismatch(
1366 "sensor_rotations has a different size than the point_data",
1367 ));
1368 }
1369
1370 let sensor_rotation_x_series = Series::new(
1371 PointDataColumnType::SensorRotationX.into(),
1372 sensor_rotations.iter().map(|r| r.i).collect::<Vec<f64>>(),
1373 );
1374 let sensor_rotation_y_series = Series::new(
1375 PointDataColumnType::SensorRotationY.into(),
1376 sensor_rotations.iter().map(|r| r.j).collect::<Vec<f64>>(),
1377 );
1378 let sensor_rotation_z_series = Series::new(
1379 PointDataColumnType::SensorRotationZ.into(),
1380 sensor_rotations.iter().map(|r| r.k).collect::<Vec<f64>>(),
1381 );
1382 let sensor_rotation_w_series = Series::new(
1383 PointDataColumnType::SensorRotationW.into(),
1384 sensor_rotations.iter().map(|r| r.w).collect::<Vec<f64>>(),
1385 );
1386 self.data_frame.with_column(sensor_rotation_x_series)?;
1387 self.data_frame.with_column(sensor_rotation_y_series)?;
1388 self.data_frame.with_column(sensor_rotation_z_series)?;
1389 self.data_frame.with_column(sensor_rotation_w_series)?;
1390
1391 Ok(())
1392 }
1393
1394 pub fn add_unique_sensor_pose(&mut self, sensor_pose: Isometry3<f64>) -> Result<(), Error> {
1395 let sensor_poses: Vec<Isometry3<f64>> = vec![sensor_pose; self.data_frame.height()];
1396 self.add_sensor_poses(sensor_poses)?;
1397
1398 Ok(())
1399 }
1400
1401 pub fn add_sensor_poses(&mut self, sensor_poses: Vec<Isometry3<f64>>) -> Result<(), Error> {
1402 if sensor_poses.len() != self.data_frame.height() {
1403 return Err(ShapeMismatch(
1404 "sensor_rotations has a different size than the point_data",
1405 ));
1406 }
1407
1408 let sensor_translations: Vec<Point3<f64>> = sensor_poses
1409 .iter()
1410 .map(|i| i.translation.vector.into())
1411 .collect();
1412 self.add_sensor_translations(sensor_translations)?;
1413
1414 let sensor_rotations: Vec<UnitQuaternion<f64>> =
1415 sensor_poses.into_iter().map(|i| i.rotation).collect();
1416 self.add_sensor_rotations(sensor_rotations)?;
1417
1418 Ok(())
1419 }
1420
1421 pub fn add_unique_color(&mut self, color: palette::Srgb<u16>) -> Result<(), Error> {
1422 let colors = vec![color; self.data_frame.height()];
1423 self.add_colors(colors)?;
1424
1425 Ok(())
1426 }
1427
1428 pub fn add_colors(&mut self, colors: Vec<palette::Srgb<u16>>) -> Result<(), Error> {
1429 if colors.len() != self.data_frame.height() {
1430 return Err(ShapeMismatch(
1431 "colors has a different size than the point_data",
1432 ));
1433 }
1434
1435 let color_red_series = Series::new(
1436 PointDataColumnType::ColorRed.into(),
1437 colors.iter().map(|p| p.red).collect::<Vec<u16>>(),
1438 );
1439 let color_green_series = Series::new(
1440 PointDataColumnType::ColorGreen.into(),
1441 colors.iter().map(|p| p.green).collect::<Vec<u16>>(),
1442 );
1443 let color_blue_series = Series::new(
1444 PointDataColumnType::ColorBlue.into(),
1445 colors.iter().map(|p| p.blue).collect::<Vec<u16>>(),
1446 );
1447 self.data_frame.with_column(color_red_series)?;
1448 self.data_frame.with_column(color_green_series)?;
1449 self.data_frame.with_column(color_blue_series)?;
1450
1451 Ok(())
1452 }
1453
1454 pub fn filter_by_row_indices(&self, row_indices: HashSet<usize>) -> Result<PointData, Error> {
1455 if row_indices.is_empty() {
1456 return Err(Error::NoRowIndices);
1457 }
1458 let row_index_max = row_indices.iter().max().unwrap();
1459 if self.data_frame.height() < *row_index_max {
1460 return Err(Error::RowIndexOutsideRange);
1461 }
1462
1463 let boolean_mask: BooleanChunked = (0..self.data_frame.height())
1464 .into_par_iter()
1465 .map(|x| row_indices.contains(&x))
1466 .collect();
1467 let filtered_data_frame = self.data_frame.filter(&boolean_mask)?;
1468 Ok(PointData::new_unchecked(filtered_data_frame))
1469 }
1470
1471 pub fn filter_by_boolean_mask(
1472 &self,
1473 boolean_mask: &BooleanChunked,
1474 ) -> Result<PointData, Error> {
1475 if self.data_frame.height() < boolean_mask.len() {
1476 return Err(Error::RowIndexOutsideRange);
1477 }
1478
1479 let filtered_data_frame = self.data_frame.filter(boolean_mask)?;
1480 Ok(PointData::new_unchecked(filtered_data_frame))
1481 }
1482
1483 pub fn filter_by_bounds(
1484 &self,
1485 bound_min: Point3<f64>,
1486 bound_max: Point3<f64>,
1487 ) -> Result<Option<PointData>, Error> {
1488 let filtered_data_frame = self
1489 .data_frame
1490 .clone()
1491 .lazy()
1492 .filter(
1493 col(PointDataColumnType::X.as_str())
1494 .gt_eq(bound_min.x)
1495 .and(col(PointDataColumnType::X.as_str()).lt_eq(bound_max.x))
1496 .and(col(PointDataColumnType::Y.as_str()).gt_eq(bound_min.y))
1497 .and(col(PointDataColumnType::Y.as_str()).lt_eq(bound_max.y))
1498 .and(col(PointDataColumnType::Z.as_str()).gt_eq(bound_min.z))
1499 .and(col(PointDataColumnType::Z.as_str()).lt_eq(bound_max.z)),
1500 )
1501 .collect()?;
1502
1503 if filtered_data_frame.height() == 0 {
1504 return Ok(None);
1505 }
1506
1507 Ok(Some(PointData::new_unchecked(filtered_data_frame)))
1508 }
1509
1510 pub fn filter_by_beam_length(
1511 &self,
1512 beam_length_min: f64,
1513 beam_length_max: f64,
1514 ) -> Result<Option<PointData>, Error> {
1515 if beam_length_min > beam_length_max {
1516 return Err(LowerBoundExceedsUpperBound);
1517 }
1518 if beam_length_min == beam_length_max {
1519 return Err(LowerBoundEqualsUpperBound);
1520 }
1521 if !self.contains_sensor_translation() {
1522 return Err(Error::NoSensorTranslationColumn);
1523 }
1524
1525 let filtered_data_frame = self
1526 .data_frame
1527 .clone()
1528 .lazy()
1529 .filter(
1530 col(PointDataColumnType::X.as_str())
1531 .sub(col(PointDataColumnType::SensorTranslationX.as_str()))
1532 .pow(2)
1533 .add(
1534 col(PointDataColumnType::Y.as_str())
1535 .sub(col(PointDataColumnType::SensorTranslationY.as_str()))
1536 .pow(2),
1537 )
1538 .add(
1539 col(PointDataColumnType::Z.as_str())
1540 .sub(col(PointDataColumnType::SensorTranslationZ.as_str()))
1541 .pow(2),
1542 )
1543 .is_between(
1544 beam_length_min * beam_length_min,
1545 beam_length_max * beam_length_max,
1546 ClosedInterval::Both,
1547 ),
1548 )
1549 .collect()?;
1550
1551 if filtered_data_frame.height() == 0 {
1552 return Ok(None);
1553 }
1554
1555 Ok(Some(PointData::new_unchecked(filtered_data_frame)))
1556 }
1557
1558 pub fn filter_by_x_min(&self, x_min: f64) -> Result<Option<PointData>, Error> {
1559 let filtered_data_frame = self
1560 .data_frame
1561 .clone()
1562 .lazy()
1563 .filter(col(PointDataColumnType::X.as_str()).gt_eq(x_min))
1564 .collect()?;
1565
1566 if filtered_data_frame.height() == 0 {
1567 return Ok(None);
1568 }
1569
1570 Ok(Some(PointData::new_unchecked(filtered_data_frame)))
1571 }
1572
1573 pub fn filter_by_x_max(&self, x_max: f64) -> Result<Option<PointData>, Error> {
1574 let filtered_data_frame = self
1575 .data_frame
1576 .clone()
1577 .lazy()
1578 .filter(col(PointDataColumnType::X.as_str()).lt_eq(lit(x_max)))
1579 .collect()?;
1580
1581 if filtered_data_frame.height() == 0 {
1582 return Ok(None);
1583 }
1584
1585 Ok(Some(PointData::new_unchecked(filtered_data_frame)))
1586 }
1587
1588 pub fn filter_by_y_min(&self, y_min: f64) -> Result<Option<PointData>, Error> {
1589 let filtered_data_frame = self
1590 .data_frame
1591 .clone()
1592 .lazy()
1593 .filter(col(PointDataColumnType::Y.as_str()).gt_eq(y_min))
1594 .collect()?;
1595
1596 if filtered_data_frame.height() == 0 {
1597 return Ok(None);
1598 }
1599
1600 Ok(Some(PointData::new_unchecked(filtered_data_frame)))
1601 }
1602
1603 pub fn filter_by_y_max(&self, y_max: f64) -> Result<Option<PointData>, Error> {
1604 let filtered_data_frame = self
1605 .data_frame
1606 .clone()
1607 .lazy()
1608 .filter(col(PointDataColumnType::Y.as_str()).lt_eq(lit(y_max)))
1609 .collect()?;
1610
1611 if filtered_data_frame.height() == 0 {
1612 return Ok(None);
1613 }
1614
1615 Ok(Some(PointData::new_unchecked(filtered_data_frame)))
1616 }
1617
1618 pub fn filter_by_z_min(&self, z_min: f64) -> Result<Option<PointData>, Error> {
1619 let filtered_data_frame = self
1620 .data_frame
1621 .clone()
1622 .lazy()
1623 .filter(col(PointDataColumnType::Z.as_str()).gt_eq(z_min))
1624 .collect()?;
1625
1626 if filtered_data_frame.height() == 0 {
1627 return Ok(None);
1628 }
1629
1630 Ok(Some(PointData::new_unchecked(filtered_data_frame)))
1631 }
1632
1633 pub fn filter_by_z_max(&self, z_max: f64) -> Result<Option<PointData>, Error> {
1634 let filtered_data_frame = self
1635 .data_frame
1636 .clone()
1637 .lazy()
1638 .filter(col(PointDataColumnType::Z.as_str()).lt_eq(lit(z_max)))
1639 .collect()?;
1640
1641 if filtered_data_frame.height() == 0 {
1642 return Ok(None);
1643 }
1644
1645 Ok(Some(PointData::new_unchecked(filtered_data_frame)))
1646 }
1647
1648 pub fn filter_by_spherical_range_min(
1649 &self,
1650 spherical_range_min: f64,
1651 ) -> Result<Option<PointData>, Error> {
1652 if !self.contains_spherical_range_column() {
1653 return Err(Error::NoSphericalRangeColumn);
1654 }
1655
1656 let filtered_data_frame = self
1657 .data_frame
1658 .clone()
1659 .lazy()
1660 .filter(col(PointDataColumnType::SphericalRange.as_str()).gt_eq(spherical_range_min))
1661 .collect()?;
1662
1663 if filtered_data_frame.height() == 0 {
1664 return Ok(None);
1665 }
1666
1667 Ok(Some(PointData::new_unchecked(filtered_data_frame)))
1668 }
1669
1670 pub fn filter_by_spherical_range_max(
1671 &self,
1672 spherical_range_max: f64,
1673 ) -> Result<Option<PointData>, Error> {
1674 if !self.contains_spherical_range_column() {
1675 return Err(Error::NoSphericalRangeColumn);
1676 }
1677
1678 let filtered_data_frame = self
1679 .data_frame
1680 .clone()
1681 .lazy()
1682 .filter(
1683 col(PointDataColumnType::SphericalRange.as_str()).lt_eq(lit(spherical_range_max)),
1684 )
1685 .collect()?;
1686
1687 if filtered_data_frame.height() == 0 {
1688 return Ok(None);
1689 }
1690
1691 Ok(Some(PointData::new_unchecked(filtered_data_frame)))
1692 }
1693
1694 pub fn filter_by_octant_index(&self, index: OctantIndex) -> Result<Option<PointData>, Error> {
1695 if !self.contains_octant_indices() {
1696 return Err(Error::NoOctantIndicesColumns);
1697 }
1698
1699 let filtered_data_frame = self
1700 .data_frame
1701 .clone()
1702 .lazy()
1703 .filter(
1704 col(PointDataColumnType::OctantIndexLevel.as_str())
1705 .eq(lit(index.level))
1706 .and(col(PointDataColumnType::OctantIndexX.as_str()).eq(lit(index.x)))
1707 .and(col(PointDataColumnType::OctantIndexY.as_str()).eq(lit(index.y)))
1708 .and(col(PointDataColumnType::OctantIndexZ.as_str()).eq(lit(index.z))),
1709 )
1710 .collect()?;
1711
1712 if filtered_data_frame.height() == 0 {
1713 return Ok(None);
1714 }
1715
1716 Ok(Some(PointData::new_unchecked(filtered_data_frame)))
1717 }
1718}
1719
1720impl PointData {
1721 pub fn resolve_data_frame(
1725 &mut self,
1726 transform_tree: &TransformTree,
1727 timestamp: &Option<DateTime<Utc>>,
1728 frame_id: &FrameId,
1729 target_frame_id: &FrameId,
1730 ) -> Result<(), Error> {
1731 let transform_id = TransformId::new(target_frame_id.clone(), frame_id.clone());
1732
1733 let isometry = if let Some(timestamp) = timestamp {
1734 transform_tree.get_transform_at_time(&transform_id, *timestamp)
1735 } else {
1736 transform_tree.get_static_transform(&transform_id)
1737 }?
1738 .isometry();
1739
1740 let transformed_points: Vec<Point3<f64>> = self
1741 .get_all_points()
1742 .par_iter()
1743 .map(|p| isometry * p)
1744 .collect();
1745 self.update_points_in_place(transformed_points)?;
1746
1747 if let Ok(all_sensor_translations) = &self.get_all_sensor_translations() {
1748 let transformed_sensor_translations: Vec<Point3<f64>> = all_sensor_translations
1749 .par_iter()
1750 .map(|p| isometry * p)
1751 .collect();
1752 self.update_sensor_translations_in_place(transformed_sensor_translations)?;
1753 }
1754
1755 if let Ok(all_sensor_rotations) = &self.get_all_sensor_rotations() {
1756 let transformed_sensor_rotations: Vec<UnitQuaternion<f64>> = all_sensor_rotations
1757 .par_iter()
1758 .map(|r| isometry.rotation * r)
1759 .collect();
1760 self.update_sensor_rotations_in_place(transformed_sensor_rotations)?;
1761 }
1762
1763 Ok(())
1764 }
1765}