use crate::ev_augmentation::{AugmentationError, AugmentationResult, Validatable};
pub const COL_X: &str = "x";
pub const COL_Y: &str = "y";
pub const COL_T: &str = "t";
pub const COL_POLARITY: &str = "polarity";
use rand::SeedableRng;
use rand_distr::Distribution;
#[cfg(unix)]
use tracing::{debug, info, instrument};
#[cfg(not(unix))]
macro_rules! debug {
($($args:tt)*) => {};
}
#[cfg(not(unix))]
macro_rules! info {
($($args:tt)*) => {};
}
#[cfg(not(unix))]
macro_rules! warn {
($($args:tt)*) => {
eprintln!("[WARN] {}", format!($($args)*))
};
}
#[cfg(not(unix))]
macro_rules! trace {
($($args:tt)*) => {};
}
#[cfg(not(unix))]
macro_rules! error {
($($args:tt)*) => {
eprintln!("[ERROR] {}", format!($($args)*))
};
}
#[cfg(not(unix))]
macro_rules! instrument {
($($args:tt)*) => {};
}
use polars::prelude::*;
#[derive(Debug, Clone)]
pub struct UniformNoiseAugmentation {
pub n_events: usize,
pub sensor_width: u16,
pub sensor_height: u16,
pub balance_polarities: bool,
pub sort_timestamps: bool,
pub seed: Option<u64>,
}
impl UniformNoiseAugmentation {
pub fn new(n_events: usize, sensor_width: u16, sensor_height: u16) -> Self {
Self {
n_events,
sensor_width,
sensor_height,
balance_polarities: false,
sort_timestamps: true,
seed: None,
}
}
pub fn with_polarity_balance(mut self, balance: bool) -> Self {
self.balance_polarities = balance;
self
}
pub fn with_sorting(mut self, sort: bool) -> Self {
self.sort_timestamps = sort;
self
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
pub fn description(&self) -> String {
format!(
"n={}, sensor={}x{}",
self.n_events, self.sensor_width, self.sensor_height
)
}
pub fn apply_to_dataframe(&self, df: LazyFrame) -> PolarsResult<LazyFrame> {
apply_uniform_noise(df, self)
}
pub fn apply_to_dataframe_eager(&self, df: DataFrame) -> PolarsResult<DataFrame> {
apply_uniform_noise(df.lazy(), self)?.collect()
}
}
impl Validatable for UniformNoiseAugmentation {
fn validate(&self) -> AugmentationResult<()> {
if self.sensor_width == 0 || self.sensor_height == 0 {
return Err(AugmentationError::InvalidSensorSize(
self.sensor_width,
self.sensor_height,
));
}
Ok(())
}
}
#[cfg_attr(unix, instrument(skip(df), fields(config = ?config)))]
pub fn apply_uniform_noise(
df: LazyFrame,
config: &UniformNoiseAugmentation,
) -> PolarsResult<LazyFrame> {
debug!("Applying uniform noise with Polars: {:?}", config);
if config.n_events == 0 {
debug!("No noise events to add");
return Ok(df);
}
let time_bounds = df
.clone()
.select([
col(COL_T).min().alias("t_min"),
col(COL_T).max().alias("t_max"),
])
.collect()?;
let t_min = time_bounds
.column("t_min")?
.get(0)?
.try_extract::<f64>()
.unwrap_or(0.0);
let t_max = time_bounds
.column("t_max")?
.get(0)?
.try_extract::<f64>()
.unwrap_or(1.0);
let (time_min, time_max) = if t_max > t_min {
(t_min, t_max)
} else {
(0.0, 1.0)
};
info!(
"Adding {} noise events in time range [{:.6}, {:.6}]",
config.n_events, time_min, time_max
);
let mut rng = if let Some(seed) = config.seed {
rand::rngs::StdRng::seed_from_u64(seed)
} else {
rand::rngs::StdRng::from_entropy()
};
use rand::Rng;
let mut noise_x = Vec::with_capacity(config.n_events);
let mut noise_y = Vec::with_capacity(config.n_events);
let mut noise_t = Vec::with_capacity(config.n_events);
let mut noise_polarity = Vec::with_capacity(config.n_events);
let x_dist = rand_distr::Uniform::new(0, config.sensor_width);
let y_dist = rand_distr::Uniform::new(0, config.sensor_height);
let t_dist = rand_distr::Uniform::new(time_min, time_max);
for i in 0..config.n_events {
noise_x.push(x_dist.sample(&mut rng) as i64);
noise_y.push(y_dist.sample(&mut rng) as i64);
noise_t.push(t_dist.sample(&mut rng));
let polarity = if config.balance_polarities {
i % 2 == 0
} else {
rng.gen_bool(0.5)
};
noise_polarity.push(if polarity { 1i64 } else { 0i64 });
}
let noise_df = df! {
COL_X => noise_x,
COL_Y => noise_y,
COL_T => noise_t,
COL_POLARITY => noise_polarity,
}?;
let combined_df = df.collect()?.vstack(&noise_df)?;
let mut result = combined_df.lazy();
if config.sort_timestamps {
result = result.sort([COL_T], SortMultipleOptions::default());
}
Ok(result)
}
pub fn apply_uniform_noise_polars(
df: LazyFrame,
config: &UniformNoiseAugmentation,
) -> PolarsResult<LazyFrame> {
apply_uniform_noise(df, config)
}
pub fn add_uniform_noise_df(
df: LazyFrame,
n_events: usize,
sensor_width: u16,
sensor_height: u16,
) -> PolarsResult<LazyFrame> {
let config = UniformNoiseAugmentation::new(n_events, sensor_width, sensor_height);
apply_uniform_noise(df, &config)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_uniform_noise_creation() {
let noise = UniformNoiseAugmentation::new(100, 640, 480);
assert_eq!(noise.n_events, 100);
assert_eq!(noise.sensor_width, 640);
assert_eq!(noise.sensor_height, 480);
assert!(!noise.balance_polarities);
assert!(noise.sort_timestamps);
}
#[test]
fn test_uniform_noise_validation() {
let valid = UniformNoiseAugmentation::new(100, 640, 480);
assert!(valid.validate().is_ok());
let invalid = UniformNoiseAugmentation::new(100, 0, 480);
assert!(invalid.validate().is_err());
let invalid = UniformNoiseAugmentation::new(100, 640, 0);
assert!(invalid.validate().is_err());
}
}