epoint_core/
point_data.rs

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 coordinate (mandatory)
48    X,
49    /// Y coordinate (mandatory)
50    Y,
51    /// Z coordinate (mandatory)
52    Z,
53    /// Identifier for an individual point (optional)
54    Id,
55    /// Coordinate frame the point is defined in (optional)
56    FrameId,
57    /// UNIX timestamp: non-leap seconds since January 1, 1970 0:00:00 UTC (optional)
58    TimestampSecond,
59    /// Nanoseconds since the last whole non-leap second
60    TimestampNanoSecond,
61    /// Representation of the pulse return magnitude
62    Intensity,
63    /// Sensor translation X coordinate
64    SensorTranslationX,
65    /// Sensor translation Y coordinate
66    SensorTranslationY,
67    /// Sensor translation Z coordinate
68    SensorTranslationZ,
69    /// Sensor rotation X coordinate
70    SensorRotationX,
71    /// Sensor rotation Y coordinate
72    SensorRotationY,
73    /// Sensor rotation Z coordinate
74    SensorRotationZ,
75    /// Sensor rotation W coordinate
76    SensorRotationW,
77    /// Red image channel value
78    ColorRed,
79    /// Green image channel value
80    ColorGreen,
81    /// Blue image channel value
82    ColorBlue,
83    /// Azimuth in the context of spherical coordinates
84    SphericalAzimuth,
85    /// Elevation in the context of spherical coordinates
86    SphericalElevation,
87    /// Range in the context of spherical coordinates
88    SphericalRange,
89    /// Level of octant index
90    OctantIndexLevel,
91    /// X index of octant
92    OctantIndexX,
93    /// Y index of octant
94    OctantIndexY,
95    /// Z index of octant
96    OctantIndexZ,
97    /// Indicates the source from which this point originated (e.g., flight line, sortie number, route number, or setup identifier)
98    /// Valid values: 1-65,535; zero is reserved.
99    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/// Wrapper around the data frame with type-safe columns.
208#[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        // check if column types are correct
220        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() != &current_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    /// Returns all points as a vector in the local coordinate frame.
628    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    /// Returns all sensor translations as points in the local coordinate frame.
674    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    /// Returns all sensor rotations as quaternions in the local coordinate frame.
694    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    /// Returns all sensor rotations as quaternions in the local coordinate frame.
716    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    /// Returns the [AABB](https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box).
831    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    /// Returns the minimum point of the [AABB](https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box).
839    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    /// Returns the maximum point of the [AABB](https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box).
847    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    /// Returns the center point of the [AABB](https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box).
855    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    /// Returns the minimum sensor translation point of the [AABB](https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box).
862    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    /// Returns the maximum sensor translation point of the [AABB](https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box).
879    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    /// Adds a sequentially increasing id column, if no column exists.
926    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    /// Derives spherical points.
943    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    /// Add a new column to this DataFrame or replace an existing one.
956    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    /// Add a new u16 column to this DataFrame or replace an existing one.
969    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    /// Add a new u32 column to this DataFrame or replace an existing one.
982    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    /// Add a new u64 column to this DataFrame or replace an existing one.
995    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    /// Add a new column to this DataFrame or replace an existing one.
1008    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    /// Add a new f64 column to this DataFrame or replace an existing one.
1021    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    /// Removes a column from the point cloud.
1034    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 derive_spherical_points_in_place(&mut self) -> Result<(), Error> {}
1087
1088    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    /// Resolves points to target_frame_id
1722    ///
1723    /// Expects a data frame with only one
1724    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}