Files
photocraft/crates/algo/examples/bench_smartselect.rs
T
Khawar Zamman WaniandFredApps a38e544faf Make Select and Mask refinement and previews tile-sparse (#10732)
* Make Select and Mask refinement and previews tile-sparse

* Preserve finite mask extent when copying sparse pixels

* Keep solid preview coverage shared during conversion

* Preserve alpha sharing and document sparse refinement limits

* Refine mixed tiles in bounded parallel batches

* Skip guide sampling for constant refinement halos

* Balance sparse refinement batches using measured queue size

* Preserve default alpha and cancel sparse refinement cooperatively

* Address review follow-ups: cancellation after completed flush, stroke assertions, and docstrings

* Fix merge: bind tile bytes to avoid temporary borrow in matting

---------

Co-authored-by: FredApps <fredapps@users.noreply.github.com>
2026-11-05 14:22:03 +01:00

100 lines
4.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Timing: smart selection on a large synthetic RGBA8 layer (default 6016×6016): a textured
//! disc (radius = 1/3 of the size) on a noisy background.
//! `cargo run --release -p photocraft-algo --example bench_smartselect [size]`
use std::time::Instant;
use photocraft_algo::matting::{self, RefineParams};
use photocraft_algo::segment::{Rng, SurfaceSampler, focus, grabcut, quick, subject};
use photocraft_algo::selection::{Region, SelectionMode, combine_region};
use photocraft_color::PixelFormat;
use photocraft_geom::Rect;
use photocraft_raster::Surface;
fn main() {
let n: i32 = std::env::args().nth(1).and_then(|v| v.parse().ok()).unwrap_or(6016);
let (c, rad) = (n as f32 / 2.0, n as f32 / 3.0);
let canvas = Rect::new(0, 0, n, n);
let t = Instant::now();
let mut s = Surface::new(PixelFormat::RGBA8);
let rows: Vec<Vec<u8>> = {
let make = |y: i32| -> Vec<u8> {
let mut rng = Rng::new(y as u64 + 1);
let mut row = Vec::with_capacity(n as usize * 4);
for x in 0..n {
let (dx, dy) = (x as f32 + 0.5 - c, y as f32 + 0.5 - c);
let inside = dx * dx + dy * dy <= rad * rad;
let noise = 0.05 * rng.normal();
let p = if inside {
let t = ((x + y) as f32 * 0.05).sin() * 0.5 + 0.5;
[0.85 + 0.05 * t + noise, 0.2 + 0.3 * t + noise, 0.15 + noise]
} else {
[0.25 + noise, 0.45 + noise, 0.7 + noise]
};
row.extend(p.map(|v| (v.clamp(0.0, 1.0) * 255.0) as u8));
row.push(255);
}
row
};
use rayon::prelude::*;
(0..n).into_par_iter().map(make).collect()
};
for (y, row) in rows.iter().enumerate() {
s.write_interleaved(Rect::new(0, y as i32, n, y as i32 + 1), row);
}
drop(rows);
println!("built {n}x{n} RGBA8 in {:.2?}", t.elapsed());
let sampler = SurfaceSampler(&s);
let truth_iou = |r: &Region| -> f32 {
let (mut i, mut u) = (0u64, 0u64);
let step = (n / 1000).max(1);
for y in (0..n).step_by(step as usize) {
for x in (0..n).step_by(step as usize) {
let (dx, dy) = (x as f32 + 0.5 - c, y as f32 + 0.5 - c);
let a = r.at(x, y) >= 0.5;
let b = dx * dx + dy * dy <= rad * rad;
i += (a && b) as u64;
u += (a || b) as u64;
}
}
i as f32 / u.max(1) as f32
};
// Quick selection: small and large brush strokes inside the disc near its edge.
for size in [30.0f32, 300.0] {
let x = c + rad - size * 2.0;
let t = Instant::now();
let r = quick::quick_select(&sampler, canvas, &[(x, c - size), (x, c + size)], size).expect("region");
println!("quick selection stroke (brush {size} px): {:.2?} (region {}x{})", t.elapsed(), r.bbox.width(), r.bbox.height());
}
// Object selection (rectangle around the disc).
let m = (rad * 1.15) as i32;
let rect = Rect::new(c as i32 - m, c as i32 - m, c as i32 + m, c as i32 + m);
let t = Instant::now();
let obj = grabcut::object_select(&sampler, canvas, rect, 160_000).expect("object");
println!("object selection: {:.2?} IoU {:.4}", t.elapsed(), truth_iou(&obj));
// Select subject.
let t = Instant::now();
let subj = subject::select_subject(&sampler, canvas).expect("subject");
println!("select subject: {:.2?} IoU {:.4}", t.elapsed(), truth_iou(&subj));
// Refine edge on a 20 px band (selection = the object result as a surface).
let sel = combine_region(None, Some(&obj), SelectionMode::Replace).expect("selection");
let p = RefineParams { radius: 20.0, smooth: 10.0, feather: 1.0, contrast: 10.0, ..Default::default() };
let t = Instant::now();
let refined = matting::refine_surface(&sampler, &sel, canvas, &p, None).expect("allocation").expect("refined");
println!("refine edge (radius 20): {:.2?}", t.elapsed());
let t = Instant::now();
let _ = matting::refine_surface(&sampler, &sel, canvas, &RefineParams { smart_radius: true, ..p }, None).expect("allocation").expect("refined");
println!("refine edge (radius 20, smart): {:.2?}", t.elapsed());
let t = Instant::now();
let dec = matting::decontaminate_surface(&s, &refined, 20.0, 100.0);
println!("decontaminate: {:.2?} ({} tiles)", t.elapsed(), dec.tile_count());
// Focus area.
let t = Instant::now();
let _ = focus::focus_area(&sampler, canvas, 0.5, 0.0);
println!("focus area: {:.2?}", t.elapsed());
}