epoint_core/
point_data.rs

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