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,
48 Y,
50 Z,
52 Id,
54 FrameId,
56 TimestampSeconds,
58 TimestampNanoSeconds,
60 Intensity,
62 SensorTranslationX,
64 SensorTranslationY,
66 SensorTranslationZ,
68 SensorRotationI,
70 SensorRotationJ,
72 SensorRotationK,
74 SensorRotationW,
76 ColorRed,
78 ColorGreen,
80 ColorBlue,
82 SphericalAzimuth,
84 SphericalElevation,
86 SphericalRange,
88 OctantIndexLevel,
90 OctantIndexX,
92 OctantIndexY,
94 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#[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 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() != ¤t_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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}