pub fn bounding_box_from_points(points: impl Iterator<Item = glam::Vec3>) -> macaw::BoundingBox {
let mut bbox = macaw::BoundingBox::nothing();
for p in points {
if p.is_finite() {
bbox.extend(p);
}
}
bbox
}
fn mean_and_sigma(values: impl Iterator<Item = glam::DVec3>) -> Option<(glam::DVec3, glam::DVec3)> {
let mut count = 0u64;
let mut sum = glam::DVec3::ZERO;
let mut sum_sq = glam::DVec3::ZERO;
for d in values {
sum += d;
sum_sq += d * d;
count += 1;
}
if count < 2 {
return None;
}
let n = count as f64;
let mean = sum / n;
let variance = (sum_sq / n - mean * mean).max(glam::DVec3::ZERO);
let sigma = glam::DVec3::new(variance.x.sqrt(), variance.y.sqrt(), variance.z.sqrt());
Some((mean, sigma))
}
pub struct PointCloudBounds {
pub bbox: macaw::BoundingBox,
pub region_of_interest: macaw::BoundingBox,
}
pub fn point_cloud_bounds(points: &[glam::Vec3]) -> PointCloudBounds {
re_tracing::profile_function_if!(points.len() > 10000);
let bbox = bounding_box_from_points(points.iter().copied());
let finite_f64 = || {
points
.iter()
.filter(|p| p.is_finite())
.map(|p| p.as_dvec3())
};
let Some((mean, sigma)) = mean_and_sigma(finite_f64()) else {
return PointCloudBounds {
bbox,
region_of_interest: bbox,
};
};
let lo = mean - 2.0 * sigma;
let hi = mean + 2.0 * sigma;
let Some((mean, sigma)) =
mean_and_sigma(finite_f64().filter(|d| d.cmpge(lo).all() && d.cmple(hi).all()))
else {
return PointCloudBounds {
bbox,
region_of_interest: bbox,
};
};
let region_of_interest = macaw::BoundingBox::from_min_max(
(mean - 2.0 * sigma).as_vec3(),
(mean + 2.0 * sigma).as_vec3(),
);
PointCloudBounds {
bbox,
region_of_interest,
}
}
#[cfg(test)]
mod tests {
use glam::Vec3;
use super::*;
#[test]
fn point_cloud_bounds_excludes_outlier_from_region_of_interest() {
let cluster_core = [
Vec3::new(0.0, 1.0, 2.0),
Vec3::new(1.0, 2.0, 0.5),
Vec3::new(2.0, 0.5, 4.0),
Vec3::new(0.5, 3.0, 1.0),
Vec3::new(1.5, 1.5, 3.0),
Vec3::new(0.2, 2.5, 0.8),
Vec3::new(2.5, 0.2, 4.5),
Vec3::new(0.8, 3.5, 2.5),
Vec3::new(1.2, 1.8, 1.5),
];
let outlier = Vec3::new(100.0, 200.0, 300.0);
let points = cluster_core
.iter()
.copied()
.chain(std::iter::once(outlier))
.collect::<Vec<_>>();
let bounds = point_cloud_bounds(&points);
assert!(
bounds.bbox.contains(outlier),
"bbox must contain outlier: {:?}",
bounds.bbox,
);
assert!(
bounds.region_of_interest.max.x < 5.0
&& bounds.region_of_interest.max.y < 5.0
&& bounds.region_of_interest.max.z < 5.0
&& bounds.region_of_interest.min.x > -1.0
&& bounds.region_of_interest.min.y > -1.0
&& bounds.region_of_interest.min.z > -1.0,
"outlier should not extend the region of interest: {:?}",
bounds.region_of_interest,
);
for point in cluster_core {
assert!(
bounds.region_of_interest.contains(point),
"inlier point should be in region of interest: {point:?} in {:?}",
bounds.region_of_interest,
);
}
}
}