blob: c442d452b01c74c149d5511fb9d05d12ccefee0e [file]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use rand::{prelude::SliceRandom, rngs::ThreadRng, thread_rng};
use std::collections::BTreeMap;
type TrueDistance = f64; // meters
type EstimatedDistance = f64; // meters
/// The data is organized as a `BTreeMap` for efficient lookup and interpolation.
struct RangingDataSet {
/// Stores ranging data in the form of (true distance, [estimated distances]) pairs.
/// The map keys are true distances in u16 centimeters.
data: BTreeMap<u16, Vec<EstimatedDistance>>,
}
impl RangingDataSet {
/// Creates a new `RangingDataSet` instance by loading ranging data.
/// Data is in a format where each entry is a tuple of
/// (true distance, estimated distance), typically representing samples from a
/// ranging sensor.
pub fn new(ranging_data: Option<Vec<(TrueDistance, EstimatedDistance)>>) -> Self {
// Use sample_ranging_data.csv if ranging_data is not provided.
let sample_ranging_data: Vec<(TrueDistance, EstimatedDistance)> =
ranging_data.unwrap_or(include!("sample_ranging_data.csv"));
// Process the sample_raning_data into BTreeMap
let mut data: BTreeMap<u16, Vec<EstimatedDistance>> = BTreeMap::new();
for (true_distance, estimated_distance) in sample_ranging_data {
// Convert true_distance into u16 centimeters
data.entry((true_distance * 100.0).round() as u16)
.or_default()
.push(estimated_distance);
}
RangingDataSet { data }
}
/// Samples an estimated distance for the given true distance.
///
/// # Arguments
///
/// * `distance` - The true distance for which an estimated distance is required.
/// * `option_rng` - An optional random number generator
/// (if not provided, a default one will be used)
///
/// # Returns
///
/// An estimated distance sampled from the dataset.
/// If the exact true distance is found in the dataset,
/// a random estimated distance is chosen from its associated values.
/// If the true distance falls between known values,
/// linear interpolation is used to estimate a distance.
/// If the true distance is outside the range of known values,
/// the distance itself is returned as the estimated distance.
pub fn sample(
&self,
distance: TrueDistance,
option_rng: Option<ThreadRng>,
) -> EstimatedDistance {
// Generate a new ThreadRng if not provided
let mut rng = option_rng.unwrap_or(thread_rng());
// Convert TrueDistance into u16 centimeters.
let distance_u16 = (distance * 100.0).round() as u16;
// Random sampling if distance is an existing data key
if let Some(vec_estimated_distance) = self.data.get(&distance_u16) {
return *vec_estimated_distance.choose(&mut rng).unwrap();
}
// Linear Interpolation if provided TrueDistance lies in between data keys
let lower = self.data.range(..=&distance_u16).next_back();
let upper = self.data.range(&distance_u16..).next();
match (lower, upper) {
(Some((lower_key, lower_vals)), Some((upper_key, upper_vals))) => {
let x1 = *lower_key as f64 / 100.0;
let y1 = *lower_vals.choose(&mut rng).unwrap();
let x2 = *upper_key as f64 / 100.0;
let y2 = *upper_vals.choose(&mut rng).unwrap();
y1 + (distance - x1) * (y2 - y1) / (x2 - x1)
}
_ => distance,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_ranging_data_set() -> RangingDataSet {
let ranging_data = vec![(0.0, 0.2), (1.0, 0.9), (2.0, 1.9), (2.0, 2.1)];
RangingDataSet::new(Some(ranging_data))
}
#[test]
fn test_sample_ranging_data_set() {
let ranging_data_set = sample_ranging_data_set();
// Linear Interpolation
assert_eq!(ranging_data_set.sample(0.5, None), 0.55);
// Exact distance found in dataset
assert!([1.9, 2.1].contains(&ranging_data_set.sample(2.0, None)));
// Out of Range
assert_eq!(ranging_data_set.sample(3.0, None), 3.0);
}
}