use crate::{Rect, V2};
pub const HISTOGRAM_BINS: usize = 256;
#[derive(Clone, Debug, PartialEq)]
pub struct Histogram {
bins: Vec<usize>,
total: usize,
min: f32,
max: f32,
}
impl Histogram {
pub fn new(num_bins: usize) -> Self {
Self {
bins: vec![0; num_bins.max(1)],
total: 0,
min: f32::INFINITY,
max: f32::NEG_INFINITY,
}
}
pub fn from_values<I: IntoIterator<Item = f32>>(values: I) -> Self {
Self::from_values_binned(values, HISTOGRAM_BINS)
}
pub fn from_values_binned<I: IntoIterator<Item = f32>>(values: I, num_bins: usize) -> Self {
values
.into_iter()
.fold(Self::new(num_bins), |mut histogram, value| {
histogram.add(value);
histogram
})
}
pub fn sample_field<F: Fn(V2) -> f32>(bounds: Rect, max_samples_per_side: usize, f: F) -> Self {
let size = bounds.size();
let n_long = max_samples_per_side.max(1);
let n_short = ((n_long as f32 * size.min_axis() / size.max_axis()).round() as usize).max(1);
let (n_x, n_y) = if size.x >= size.y {
(n_long, n_short)
} else {
(n_short, n_long)
};
let cell_centers = |n: usize| (0..n).map(move |i| (i as f32 + 0.5) / n as f32);
let f = &f;
Self::from_values(
cell_centers(n_y).flat_map(|v| {
cell_centers(n_x).map(move |u| f(bounds.bl() + size * V2::new(u, v)))
}),
)
}
pub fn add(&mut self, value: f32) {
let value = value.clamp(0.0, 1.0);
let bin = ((value * self.bins.len() as f32) as usize).min(self.bins.len() - 1);
self.bins[bin] += 1;
self.total += 1;
self.min = self.min.min(value);
self.max = self.max.max(value);
}
pub fn bins(&self) -> &[usize] {
&self.bins
}
pub fn total(&self) -> usize {
self.total
}
pub fn min(&self) -> f32 {
if self.total == 0 {
0.0
} else {
self.min
}
}
pub fn max(&self) -> f32 {
if self.total == 0 {
0.0
} else {
self.max
}
}
pub fn mean(&self) -> f32 {
if self.total == 0 {
return 0.0;
}
let sum: f32 = self
.bins
.iter()
.enumerate()
.map(|(i, &count)| self.bin_center(i) * count as f32)
.sum();
sum / self.total as f32
}
pub fn median(&self) -> f32 {
self.quantile(0.5)
}
pub fn cdf(&self, value: f32) -> f32 {
if self.total == 0 {
return 0.0;
}
let position = value.clamp(0.0, 1.0) * self.bins.len() as f32;
let bin = (position as usize).min(self.bins.len() - 1);
let fraction_in_bin = position - bin as f32;
let below: usize = self.bins[..bin].iter().sum();
(below as f32 + fraction_in_bin * self.bins[bin] as f32) / self.total as f32
}
pub fn quantile(&self, p: f32) -> f32 {
if self.total == 0 {
return 0.0;
}
if p <= 0.0 {
return self.min;
}
if p >= 1.0 {
return self.max;
}
let target = p * self.total as f32;
let mut below = 0;
for (i, &count) in self.bins.iter().enumerate() {
if count > 0 && (below + count) as f32 >= target {
let fraction_in_bin = (target - below as f32) / count as f32;
return (i as f32 + fraction_in_bin) / self.bins.len() as f32;
}
below += count;
}
self.max
}
pub fn levels(&self, num_levels: usize) -> Vec<f32> {
(1..num_levels)
.map(|i| self.quantile(i as f32 / num_levels as f32))
.collect()
}
pub fn equalize_fn(&self) -> impl Fn(f32) -> f32 + '_ {
move |value| self.cdf(value)
}
pub fn stretch_fn(&self, low: f32, high: f32) -> impl Fn(f32) -> f32 {
let (from, to) = (self.quantile(low), self.quantile(high));
let range = to - from;
move |value| {
if range <= 0.0 {
0.0
} else {
((value - from) / range).clamp(0.0, 1.0)
}
}
}
fn bin_center(&self, bin: usize) -> f32 {
(bin as f32 + 0.5) / self.bins.len() as f32
}
}