Estimate the linear slope with error minimization.

This works better than doing it analytically because of
floating point math, and give significantly lower error on
the fitted curve.
This commit is contained in:
Nathan Vegdahl 2022-04-23 09:26:24 -07:00
parent 8563e1cf0c
commit beaefbf62f

View File

@ -1,28 +1,35 @@
use crate::linear_log::log_to_linear as log_to_lin; use crate::linear_log::log_to_linear as log_to_lin;
pub fn find_parameters(lut: &[f32]) { pub fn find_parameters(lut: &[f32]) {
let lin_norm = 1.0 / (lut.len() - 1) as f64; let norm_div = (lut.len() - 1) as f64;
// Collect a large subset of LUT points as (log, lin) coordinates.
let coords: Vec<(f64, f64)> = lut
.iter()
.enumerate()
.step_by(lut.len() / 4096)
.map(|(i, y)| (i as f64 / norm_div, *y as f64))
.collect();
// Compute the stuff that we can without estimation. // Compute the stuff that we can without estimation.
let offset = lut[0] as f64; let offset = lut[0] as f64;
let end = lut[lut.len() - 1] as f64; let end = lut[lut.len() - 1] as f64;
let slope = { let slope = {
// We take the difference of points near zero for increased accuracy. // Find the point closest to linear zero that's not the first point.
let (mut i, _) = lut.iter().enumerate().find(|(_, y)| **y > 0.0).unwrap(); let p = (&coords[1..]).iter().fold((1000.0f64, 1000.0f64), |a, b| {
if i == 0 { if a.1.abs() < b.1.abs() {
i += 1; a
} } else {
lin_norm / (lut[i] as f64 - lut[i - 1] as f64) *b
}
});
optimize(
|s: f64| ((p.0 / s) + offset - p.1).abs(),
[0.000001, 10000000.0],
100,
)
}; };
// Collect LUT points as (x, y) coordinates.
let coords: Vec<(f64, f64)> = lut
.iter()
.enumerate()
.step_by(lut.len() / 256)
.map(|(i, y)| (i as f64 * lin_norm, *y as f64))
.collect();
// Do the fitting. // Do the fitting.
let base = optimize( let base = optimize(
|v: f64| { |v: f64| {
@ -45,15 +52,10 @@ pub fn find_parameters(lut: &[f32]) {
let mut avg_err = 0.0f64; let mut avg_err = 0.0f64;
let mut avg_samples = 0usize; let mut avg_samples = 0usize;
for (x, y) in coords.iter().copied() { for (x, y) in coords.iter().copied() {
// Only record error for the log part of the curve because we let e = (log_to_lin(x, offset, slope, log_offset, base) - y).abs() / y.abs();
// computed the linear segment's slope and offset analytically, max_err = max_err.max(e);
// and the relative error of points very near zero isn't reliable. avg_err += e;
if y > transition.0 { avg_samples += 1;
let e = (log_to_lin(x, offset, slope, log_offset, base) - y).abs() / y.abs();
max_err = max_err.max(e);
avg_err += e;
avg_samples += 1;
}
} }
avg_err /= avg_samples as f64; avg_err /= avg_samples as f64;