From e894568cf87db191adfee9f36162565151ee4232 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Mon, 25 Apr 2022 13:06:44 -0700 Subject: [PATCH] Print more fit statistics, as well as a more standard equation form. Also print the code in pseudo code rather than Rust. Also a few tweaks to other areas of the code. --- README.md | 4 +++- src/linear_log.rs | 50 +++++++++++++++++++-------------------------- src/optimize_log.rs | 30 +++++++++++++++------------ 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 88f1064..5323118 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,9 @@ Next, run the processed OpenEXR image through lut_extractor like this: lut_extractor -i filename.exr ``` -This will produce two LUT files, one in `.cube` format and one in `.spi1d` format. It will also print Rust code for an attempted analytic fit of the LUT. The printed error percentages only apply to the Rust code, not to the LUT. The LUT is always accurate as long as you followed the steps correctly. +This will produce two LUT files, one in `.cube` format and one in `.spi1d` format. + +It also attempts an analytic fit to the LUT, but this only works well for specific kinds of transfer functions. Error statistics and pseudo code of the fit are printed. (Note: Max Relative Error is the most relevant statistic, and should be below 0.01 for an okay fit and below 0.001 for a good fit.) And that's it! diff --git a/src/linear_log.rs b/src/linear_log.rs index d195479..76ce2a2 100644 --- a/src/linear_log.rs +++ b/src/linear_log.rs @@ -43,7 +43,7 @@ pub fn find_log_offset_for_end(end: f64, line_offset: f64, slope: f64, base: f64 let mut offset_up = 10.0; let mut offset_down = -10.0; - for _ in 0..54 { + for _ in 0..100 { let log_offset = (offset_up + offset_down) * 0.5; if linear_to_log(end, line_offset, slope, log_offset, base) > 1.0 { offset_up = log_offset; @@ -57,46 +57,38 @@ pub fn find_log_offset_for_end(end: f64, line_offset: f64, slope: f64, base: f64 //------------------------------------------------------------- -/// Generates Rust code for a both linear-to-log and log-to-linear +/// Generates psuedo code for both linear-to-log and log-to-linear /// functions with the given parameters. pub fn generate_code(line_offset: f64, slope: f64, log_offset: f64, base: f64) -> String { let transition = 1.0 / (slope * base.ln()); let k1 = transition + log_offset; - let k2 = (transition - line_offset + log_offset) * slope; + let k2 = (k1 - line_offset) * slope; let l = (transition - line_offset + log_offset) * slope - transition.log(base); format!( r#" -const A: f32 = {}; -const B: f32 = {}; -const C: f32 = {}; -const D: f32 = {}; -const E: f32 = {}; +A = {} (line slope) +B = {} (line y offset) +C = {} (log y scale) +D = {} (log x offset) +E = {} (log y offset) -pub fn linear_to_log(x: f32) -> f32 {{ - const P: f32 = {}; +linear_to_log(x) = + if x <= {}: + A * x + B + else: + C * ln(x + D) + E - if x <= P {{ - (x - A) * B - }} else {{ - ((x - C).log2() / D) + E - }} -}} - -pub fn log_to_linear(x: f32) -> f32 {{ - const P: f32 = {}; - - if x <= P {{ - (x / B) + A - }} else {{ - ((x - E) * D).exp2() + C - }} -}} +log_to_linear(x) = + if x <= {}: + (x - B) / A + else: + e^((x - E) / C) - D "#, - line_offset, slope, - log_offset, - base.log2(), + -line_offset * slope, + 1.0 / base.ln(), + -log_offset, l, k1, k2, diff --git a/src/optimize_log.rs b/src/optimize_log.rs index b556ef3..bb647c3 100644 --- a/src/optimize_log.rs +++ b/src/optimize_log.rs @@ -34,12 +34,12 @@ pub fn find_parameters(lut: &[f32]) { let base = optimize( |v: f64| { let log_offset = crate::linear_log::find_log_offset_for_end(end, offset, slope, v); - let mut sqr_err = 0.0f64; + let mut rel_err = 0.0f64; for (x, y) in coords.iter().copied() { let e = (log_to_lin(x, offset, slope, log_offset, v) - y).abs() / y.abs(); - sqr_err += e * e; + rel_err += e; } - sqr_err + rel_err }, [1.5, 10000000.0], 100, @@ -49,25 +49,29 @@ pub fn find_parameters(lut: &[f32]) { // Calculate the error of our model. let mut max_err = 0.0f64; let mut avg_err = 0.0f64; + let mut max_rel_err = 0.0f64; + let mut avg_rel_err = 0.0f64; let mut avg_samples = 0usize; for (x, y) in coords.iter().copied() { - let e = (log_to_lin(x, offset, slope, log_offset, base) - y).abs() / y.abs(); + let e = (log_to_lin(x, offset, slope, log_offset, base) - y).abs(); max_err = max_err.max(e); avg_err += e; + let rel_e = e / y.abs(); + max_rel_err = max_rel_err.max(rel_e); + avg_rel_err += rel_e; avg_samples += 1; } avg_err /= avg_samples as f64; + avg_rel_err /= avg_samples as f64; + + println!("Fitted function statistics:"); + println!(" Max Relative Error: {}", max_rel_err); + println!(" Max Absolute Error: {}", max_err); + println!(" Avg Relative Error: {}", avg_rel_err); + println!(" Avg Absolute Error: {}", avg_err); println!( - "Max Relative Error: {:.4}%\nAvg Relative Error: {:.4}%", - max_err * 100.0, - avg_err * 100.0 - ); - - // dbg!(offset, log_offset, slope, base, end); - - println!( - "{}", + "\nFitted function pseudo code:\n{}", crate::linear_log::generate_code(offset, slope, log_offset, base), ); }