Browse Source

wrote cost evaluator

main
Thomas Johnson 11 months ago
parent
commit
8e259b956f
  1. 10
      src/main.rs
  2. 88
      src/map.rs

10
src/main.rs

@ -22,10 +22,11 @@ fn main() {
let mut image_out: ImageBuffer<Rgb<f32>, Vec<f32>> = ImageBuffer::from_fn(dims.0, dims.1, |_, _| Rgb([0.0, 0.0, 0.0]));
let mut rng = rand::thread_rng();
let cfg = MapGeneticConfig {
dims,
initial_size: 50,
};
let cfg = MapGeneticConfig::new(
image_in.clone(),
50,
20,
);
let map = cfg.create_map(&mut rng, 50);
for i in 0..50 {
map.apply_add(&image_in, &mut image_out, dims);
@ -57,6 +58,7 @@ fn main() {
// to_corner: (0.0, 1.0),
// to_extents: [(1.0, -0.5), (0.0, -0.5)],
// };
println!("cost: {}", map.cost(&cfg));
correct_image_brightness(&mut image_out, dims, 0.5, 0.15);
let image_out_u16: ImageBuffer<Rgb<u16>, Vec<u16>> = ImageBuffer::from_fn(dims.0, dims.1, |x, y| {
let pix = image_out.get_pixel(x, y);

88
src/map.rs

@ -3,13 +3,22 @@ use core::fmt::Debug;
use core::cell::Cell;
use rand::{Rng, distributions::{Uniform, WeightedIndex}};
use rand_distr::{Distribution, Bernoulli};
use image::{Rgb, GenericImage};
use image::{Rgb, GenericImage, ImageBuffer};
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct Map
{
transforms: Vec<Transform>,
cached_cost: Cell<Option<f64>>,
cached_cost: Cell<Option<f32>>,
}
impl Clone for Map {
fn clone(&self) -> Self {
Self {
transforms: self.transforms.clone(),
cached_cost: Cell::new(None),
}
}
}
impl Map {
@ -27,11 +36,37 @@ impl Map {
}
}
pub fn cost(&self) -> f64 {
pub fn cost(&self, cfg: &MapGeneticConfig) -> f32 {
if let Some(c) = self.cached_cost.get() {
c
} else {
unimplemented!();
let mut input = ImageBuffer::from_fn(cfg.dims.0, cfg.dims.1, |_, _| Rgb([0.0, 0.0, 0.0]));
let mut output = input.clone();
for _ in 0..cfg.num_iters {
self.apply_add(&input, &mut output, cfg.dims);
core::mem::swap(&mut input, &mut output);
output = ImageBuffer::from_fn(input.width(), input.height(), |_, _| Rgb([0.0, 0.0, 0.0]));
}
// spooky least squares math, transcribed from my notebook where i solved
// this problem. basically, compute the minimum square error we can get
// if we are allowed to scale this image and add a constant to it.
let a_a = image_dot_product(&input, &input);
let sum_a = image_sum(&input);
let denom = a_a * cfg.ndof as f32 - sum_a * sum_a;
// not very stable, but i think that it doesn't matter here, since we
// just want to know if it's large, indicating deconvergence
let variance = a_a / cfg.ndof as f32 - sum_a * sum_a / (cfg.ndof * cfg.ndof) as f32;
if denom == 0.0 || variance > 50.0 {
// if the image is entirely zero or it doesn't appear to converge, return infinite cost
self.cached_cost.set(Some(f32::INFINITY));
f32::INFINITY
} else {
let a_x = image_dot_product(&input, &cfg.goal_image);
let numer = a_x * a_x * cfg.ndof as f32 - 2.0 * a_x * sum_a * cfg.goal_sum + cfg.goal_sum * cfg.goal_sum * a_a;
let cost = cfg.goal_dot - numer / denom;
self.cached_cost.set(Some(cost));
cost
}
}
}
}
@ -39,8 +74,17 @@ impl Map {
#[derive(Debug)]
pub struct MapGeneticConfig
{
pub dims: (u32, u32),
dims: (u32, u32),
pub initial_size: usize,
pub num_iters: usize,
goal_image: ImageBuffer<Rgb<f32>, Vec<f32>>,
// dot product of goal image with itself
goal_dot: f32,
// goal image sum of all elements
goal_sum: f32,
// number of degrees of freedom (vector dimension)
ndof: usize,
}
impl Genetic for Map
@ -108,12 +152,28 @@ impl Genetic for Map
new
}
fn compare<R: Rng>(&self, _rng: &mut R, other: &Self, _config: &Self::Configuration) -> std::cmp::Ordering {
self.cost().partial_cmp(&other.cost()).unwrap_or(std::cmp::Ordering::Equal)
fn compare<R: Rng>(&self, _rng: &mut R, other: &Self, config: &Self::Configuration) -> std::cmp::Ordering {
self.cost(&config).partial_cmp(&other.cost(&config)).unwrap_or(std::cmp::Ordering::Equal)
}
}
impl MapGeneticConfig {
pub fn new(goal_image: ImageBuffer<Rgb<f32>, Vec<f32>>, initial_size: usize, num_iters: usize) -> Self {
let dims = goal_image.dimensions();
let goal_dot = image_dot_product(&goal_image, &goal_image);
let goal_sum = image_sum(&goal_image);
let ndof = (dims.0 * dims.1 * 3) as usize;
MapGeneticConfig {
dims,
initial_size,
num_iters,
goal_image,
goal_dot,
goal_sum,
ndof,
}
}
pub fn create_map<R: Rng>(&self, rng: &mut R, tf_count: usize) -> Map {
let mut transforms = vec![];
for _ in 0..tf_count {
@ -171,3 +231,15 @@ impl MapGeneticConfig {
}
}
}
/// Treat the entire image as a vector, and find the dot product of two images.
pub fn image_dot_product(x: &ImageBuffer<Rgb<f32>, Vec<f32>>, y: &ImageBuffer<Rgb<f32>, Vec<f32>>) -> f32 {
// do the sum row-wise for numerical stability
x.rows().zip(y.rows()).map(|(x, y)| x.zip(y).map(|(&Rgb([xr, xg, xb]), &Rgb([yr, yg, yb]))| xr * yr + xg * yg + xb * yb).sum::<f32>()).sum()
}
/// Compute the sum of all the degrees of freedom (subpixel values) of an image. Equivalent to the dot product with the vector of all 1s.
pub fn image_sum(x: &ImageBuffer<Rgb<f32>, Vec<f32>>) -> f32 {
// do the sum row-wise for numerical stability
x.rows().map(|x| x.map(|&Rgb([r, g, b])| r + g + b).sum::<f32>()).sum()
}
Loading…
Cancel
Save