Files
hcie-rust-v3.05/hcie-io/examples/tune_mae_inner_shadow_fine.rs
2026-07-09 02:59:53 +03:00

253 lines
9.9 KiB
Rust

#![allow(dead_code, unused_imports, unused_variables, unused_macros, unreachable_patterns)]
/// Fine-grid inner-shadow MAE tuning around the best parameters found by coarse grid.
///
/// Coarse grid result: blur_factor=6.0, passes=1, choke_scale=0.00, mask_strength=0.75
/// This script tests finer ranges around those values.
use std::io::Write;
const GT_DIR: &str = "_tmp/inner_shadow_png_set";
const CANVAS: u32 = 256;
const MARGIN: u32 = 64;
const RESULTS_FILE: &str = "inner_shadow_fine_results.txt";
struct Sample {
name: String,
angle: f32,
distance: f32,
size: f32,
choke: f32,
opacity: f32,
noise: f32,
ref_pixels: Vec<u8>,
content_mask: Vec<bool>,
}
fn main() {
let entries: Vec<_> = std::fs::read_dir(GT_DIR)
.unwrap_or_else(|e| panic!("cannot read {}: {}", GT_DIR, e))
.filter_map(|e| e.ok())
.filter(|e| {
let s = e.file_name();
let s = s.to_string_lossy();
s.starts_with("is_a") && s.ends_with(".png")
})
.collect();
println!("Found {} PNGs in {}", entries.len(), GT_DIR);
let mut samples: Vec<Sample> = Vec::new();
let mut skipped_noise = 0;
for entry in &entries {
let path = entry.path();
let stem = path.file_stem().unwrap().to_string_lossy().to_string();
let params = match parse_filename(&stem) {
Some(p) => p,
None => { eprintln!(" SKIP: {}", stem); continue; }
};
let (angle, distance, size, choke, opacity, noise, _blend) = params;
if noise > 0.0 {
skipped_noise += 1;
continue;
}
let img = match image::open(&path) {
Ok(i) => i,
Err(e) => { eprintln!(" SKIP {}: {}", stem, e); continue; }
};
let rgba = img.to_rgba8();
let raw = rgba.into_raw();
let npix = (CANVAS * CANVAS) as usize;
let mut content_mask = vec![false; npix];
for i in 0..npix {
let r = raw[i * 4];
let g = raw[i * 4 + 1];
let b = raw[i * 4 + 2];
let a = raw[i * 4 + 3];
content_mask[i] = a > 0 || !(r == 255 && g == 255 && b == 255);
}
samples.push(Sample {
name: stem,
angle, distance, size, choke, opacity, noise,
ref_pixels: raw,
content_mask,
});
}
println!("Loaded {} samples (skipped {} with noise>0)", samples.len(), skipped_noise);
if samples.is_empty() {
eprintln!("No valid samples found.");
std::process::exit(1);
}
let content_count = samples[0].content_mask.iter().filter(|&&b| b).count();
println!("Content pixels per image: {} / {} ({:.1}%)",
content_count, npix(), content_count as f64 / npix() as f64 * 100.0);
let rect_alpha: Vec<u8> = {
let n = npix();
let mut a = vec![0u8; n];
for y in 0..CANVAS {
for x in 0..CANVAS {
if x >= MARGIN && x < CANVAS - MARGIN && y >= MARGIN && y < CANVAS - MARGIN {
a[(y * CANVAS + x) as usize] = 255;
}
}
}
a
};
let shadow_color: [u8; 4] = [220, 30, 30, 255];
// Fine grid around best: bf=6.0, p=1, cs=0.00, ms=0.75
let blur_factors: &[f32] = &[4.0, 4.5, 5.0, 5.5, 5.8, 6.0, 6.2, 6.5, 7.0, 8.0, 10.0];
let passes_list: &[i32] = &[1, 2];
let choke_scales: &[f32] = &[0.0, 0.1, 0.2, 0.3];
let mask_strengths: &[f32] = &[0.5, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 1.0];
let total_combos = blur_factors.len() * passes_list.len() * choke_scales.len() * mask_strengths.len();
println!("Fine grid: {} combos x {} samples = {} evals", total_combos, samples.len(), total_combos * samples.len());
std::io::stdout().flush().ok();
let mut best_avg_mae = f64::MAX;
let mut best_params = (0.0f32, 0i32, 0.0f32, 0.0f32);
let mut eval_count = 0usize;
// Also track per-angle best
let angles: Vec<f32> = {
let mut set = std::collections::BTreeSet::new();
for s in &samples { set.insert(s.angle as i32); }
set.into_iter().map(|a| a as f32).collect()
};
let mut per_angle_best: std::collections::HashMap<i32, (f64, f32, i32, f32, f32)> = std::collections::HashMap::new();
for &blur_factor in blur_factors {
for &passes in passes_list {
for &choke_scale in choke_scales {
for &mask_strength in mask_strengths {
let mut total_mae = 0.0f64;
for s in &samples {
let shadow = hcie_fx::tuned::generate_inner_shadow_tuned(
&rect_alpha, CANVAS, CANVAS,
s.angle, s.distance, s.choke, s.size,
shadow_color,
blur_factor, passes, choke_scale, mask_strength,
);
let px = npix();
let mut out = vec![0u8; px * 4];
for i in 0..px {
if rect_alpha[i] > 0 {
out[i * 4] = 0;
out[i * 4 + 1] = 0;
out[i * 4 + 2] = 0;
out[i * 4 + 3] = 255;
}
}
for i in 0..px {
let sa = shadow[i * 4 + 3];
if sa == 0 { continue; }
let dst = [out[i * 4], out[i * 4 + 1], out[i * 4 + 2], out[i * 4 + 3]];
let src = [shadow[i * 4], shadow[i * 4 + 1], shadow[i * 4 + 2], sa];
let blended = hcie_blend::blend_pixels(dst, src, hcie_blend::BlendMode::Normal, s.opacity);
out[i * 4..i * 4 + 4].copy_from_slice(&blended);
}
let mut diff_sum = 0.0f64;
let mut content_pixels = 0u64;
for i in 0..px {
if !s.content_mask[i] { continue; }
content_pixels += 1;
for c in 0..4 {
diff_sum += (out[i * 4 + c] as i32 - s.ref_pixels[i * 4 + c] as i32).abs() as f64;
}
}
if content_pixels > 0 {
let mae = diff_sum / content_pixels as f64 / 4.0;
total_mae += mae;
// Per-angle tracking
let angle_key = s.angle as i32;
let entry = per_angle_best.entry(angle_key).or_insert((f64::MAX, 0.0, 0, 0.0, 0.0));
if mae < entry.0 {
*entry = (mae, blur_factor, passes, choke_scale, mask_strength);
}
}
}
eval_count += 1;
let avg_mae = total_mae / samples.len() as f64;
if avg_mae < best_avg_mae {
best_avg_mae = avg_mae;
best_params = (blur_factor, passes, choke_scale, mask_strength);
println!("*** [{}/{}] NEW BEST avg MAE = {:.4} (bf={:.1}, p={}, cs={:.2}, ms={:.2})",
eval_count, total_combos, best_avg_mae, blur_factor, passes, choke_scale, mask_strength);
std::io::stdout().flush().ok();
} else if eval_count % 100 == 0 {
println!(" [{}/{}] avg={:.4} best={:.4}", eval_count, total_combos, avg_mae, best_avg_mae);
std::io::stdout().flush().ok();
}
}
}
}
}
let (bbf, bp, bcs, bms) = best_params;
println!("\n=== RESULT ===");
println!("Best avg MAE = {:.4}", best_avg_mae);
println!(" blur_factor = {:.1}", bbf);
println!(" passes = {}", bp);
println!(" choke_scale = {:.2}", bcs);
println!(" mask_strength = {:.2}", bms);
println!("\n=== PER-ANGLE BEST ===");
for angle in &angles {
let key = *angle as i32;
if let Some((mae, bf, p, cs, ms)) = per_angle_best.get(&key) {
println!(" angle={:.0}: MAE={:.4} bf={:.1} p={} cs={:.2} ms={:.2}",
angle, mae, bf, p, cs, ms);
}
}
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true).write(true).truncate(true).open(RESULTS_FILE)
{
let _ = writeln!(file,
"blur_factor={:.1} passes={} choke_scale={:.2} mask_strength={:.2}\n\
avg_mae={:.4} samples={} combos={}",
bbf, bp, bcs, bms, best_avg_mae, samples.len(), total_combos);
let _ = writeln!(file, "\n=== PER-ANGLE BEST ===");
for angle in &angles {
let key = *angle as i32;
if let Some((mae, bf, p, cs, ms)) = per_angle_best.get(&key) {
let _ = writeln!(file, " angle={:.0}: MAE={:.4} bf={:.1} p={} cs={:.2} ms={:.2}",
angle, mae, bf, p, cs, ms);
}
}
}
}
fn npix() -> usize { (CANVAS * CANVAS) as usize }
fn parse_filename(name: &str) -> Option<(f32, f32, f32, f32, f32, f32, String)> {
let parts: Vec<&str> = name.split('_').collect();
if parts.len() < 8 || parts[0] != "is" { return None; }
let angle = parts[1].strip_prefix('a')?.parse::<f32>().ok()?;
let distance = parts[2].strip_prefix('d')?.parse::<f32>().ok()?;
let size = parts[3].strip_prefix('s')?.parse::<f32>().ok()?;
let choke = parts[4].strip_prefix('c')?.parse::<f32>().ok()?;
let opacity = parts[5].strip_prefix("op")?.parse::<f32>().ok()? / 100.0;
let noise = parts[6].strip_prefix('n')?.parse::<f32>().ok()?;
let blend = parts[7].to_string();
Some((angle, distance, size, choke, opacity, noise, blend))
}