anyd 0.1.2

From-scratch encoding and decoding of 1D and 2D barcodes with lossless round-trip and live-video detection
Documentation
// Diagnostic: what does detect::locate find on a real photo?
use anyd::detect::{LocateOptions, locate};
fn main() {
    let path = std::env::args().nth(1).expect("usage: locdebug <png>");
    let rgba = oxideav_png::decode_png_to_rgba(&std::fs::read(&path).unwrap()).unwrap();
    let (w, h) = (rgba.width as usize, rgba.height as usize);
    let luma: Vec<u8> = rgba
        .data
        .chunks_exact(4)
        .map(|p| ((p[0] as u32 * 299 + p[1] as u32 * 587 + p[2] as u32 * 114) / 1000) as u8)
        .collect();
    let frame = anyd::GrayFrame::new(&luma, w, h).unwrap();
    // Env overrides for quick experiments: LOC_MAX (max_candidates), LOC_FRAC
    // (max_region_frac), LOC_DENSITY (edge_density).
    let env = |k: &str| std::env::var(k).ok().and_then(|v| v.parse().ok());
    let mut opts = LocateOptions::default();
    if let Some(v) = env("LOC_MAX") {
        opts.max_candidates = v as usize;
    }
    if let Some(v) = env("LOC_FRAC") {
        opts.max_region_frac = v;
    }
    if let Some(v) = env("LOC_DENSITY") {
        opts.edge_density = v;
    }
    if let Some(v) = env("LOC_DOWNSCALE") {
        opts.downscale = v as usize;
    }
    let cands = locate(&frame, &opts);
    let thr = anyd::imgproc::threshold::otsu_threshold(&frame);
    println!("frame {w}x{h}: {} candidates (otsu {thr})", cands.len());
    for c in &cands {
        let cr = c.location.outline.corners;
        let (xs, ys): (Vec<f32>, Vec<f32>) = (
            cr.iter().map(|p| p.x).collect(),
            cr.iter().map(|p| p.y).collect(),
        );
        let (x0, y0) = (
            xs.iter().cloned().fold(f32::MAX, f32::min),
            ys.iter().cloned().fold(f32::MAX, f32::min),
        );
        let (x1, y1) = (
            xs.iter().cloned().fold(0.0, f32::max),
            ys.iter().cloned().fold(0.0, f32::max),
        );
        let fam = c
            .symbology
            .map(|s| format!("{:?}", s.dimension()))
            .unwrap_or("?".into());
        // Dark fraction of the candidate box under the global Otsu split — the cheap
        // "is this barcode-like ink coverage?" signal the locator gates on.
        let (bx0, by0) = (x0.max(0.0) as usize, y0.max(0.0) as usize);
        let (bx1, by1) = ((x1 as usize).min(w), (y1 as usize).min(h));
        let mut dark = 0u64;
        for yy in by0..by1 {
            for xx in bx0..bx1 {
                if luma[yy * w + xx] <= thr {
                    dark += 1;
                }
            }
        }
        let area = ((bx1 - bx0) * (by1 - by0)).max(1) as f64;
        println!(
            "  {fam:8} ({:.0},{:.0})-({:.0},{:.0})  {:.0}x{:.0}  dark {:.2}",
            x0,
            y0,
            x1,
            y1,
            x1 - x0,
            y1 - y0,
            dark as f64 / area
        );
    }
}