mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
Find small objects in large documents with the Object Finder
Fixes #6576 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 5.5
parent
2a952f164c
commit
e5a4af7648
@@ -144,6 +144,10 @@ pub const OBJECT_AT_WORK_PX: usize = 60_000;
|
||||
/// foreground, the cursor definite foreground, outside the box definite background), the part
|
||||
/// connected to the cursor is kept and the boundary is re-cut at full resolution, as the
|
||||
/// rectangle mode does.
|
||||
///
|
||||
/// When the whole-canvas pass finds nothing in a large document (an object only a few working
|
||||
/// pixels wide blurs into its surroundings), the search is repeated at finer steps in windows of
|
||||
/// the same working size around the point, down to full resolution.
|
||||
pub fn object_at(sampler: &dyn Sampler, canvas: Rect, x: f32, y: f32, work_px: usize) -> Option<Region> {
|
||||
if canvas.width() < 4 || canvas.height() < 4 || !x.is_finite() || !y.is_finite() {
|
||||
return None;
|
||||
@@ -152,7 +156,30 @@ pub fn object_at(sampler: &dyn Sampler, canvas: Rect, x: f32, y: f32, work_px: u
|
||||
if !canvas.contains(px, py) {
|
||||
return None;
|
||||
}
|
||||
let step = super::scale_for(canvas, work_px.max(1024));
|
||||
let work_px = work_px.max(1024);
|
||||
let mut step = super::scale_for(canvas, work_px);
|
||||
if let Some(r) = object_in(sampler, canvas, px, py, step) {
|
||||
return Some(r);
|
||||
}
|
||||
// Side (working pixels) of the square windows of the finer passes.
|
||||
let side = (work_px as f64).sqrt() as usize;
|
||||
while step > 1 {
|
||||
step = (step / 4).max(1);
|
||||
let half = (side.saturating_mul(step) / 2).min(i32::MAX as usize / 4) as i32;
|
||||
let window = Rect::new(px.saturating_sub(half), py.saturating_sub(half), px.saturating_add(half), py.saturating_add(half)).intersect(&canvas);
|
||||
if window.width() < 4 || window.height() < 4 {
|
||||
continue;
|
||||
}
|
||||
if let Some(r) = object_in(sampler, window, px, py, step) {
|
||||
return Some(r);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// One [`object_at`] pass over `canvas` (the document or a window of it) at working step `step`;
|
||||
/// `(px, py)` lies inside `canvas`.
|
||||
fn object_in(sampler: &dyn Sampler, canvas: Rect, px: i32, py: i32, step: usize) -> Option<Region> {
|
||||
let img = sampler.rgb_scaled(canvas, step);
|
||||
let (w, h) = (img.w, img.h);
|
||||
if w < 2 || h < 2 {
|
||||
|
||||
@@ -762,6 +762,28 @@ fn object_at_on_the_background_or_off_canvas_finds_nothing() {
|
||||
assert!(grabcut::object_at(&s, canvas, 400.0, 10.0, 0).is_none());
|
||||
}
|
||||
|
||||
/// #6576: a 24 px object in a 4000×3000 document is found (a 1000×800 one still is).
|
||||
#[test]
|
||||
fn object_at_finds_small_object_in_large_canvas() {
|
||||
let (w, h) = (4000usize, 3000usize);
|
||||
let canvas = Rect::new(0, 0, w as i32, h as i32);
|
||||
for (ox, oy, ow, oh) in [(188usize, 138usize, 24usize, 24usize), (1500, 1100, 1000, 800)] {
|
||||
let inside = |x: usize, y: usize| x >= ox && x < ox + ow && y >= oy && y < oy + oh;
|
||||
let img = RgbImage::from_fn(w, h, |x, y| if inside(x, y) { [0.8, 0.1, 0.1] } else { [0.9, 0.9, 0.9] });
|
||||
let truth: Vec<bool> = (0..w * h).map(|i| inside(i % w, i / w)).collect();
|
||||
let s = ImageSampler { img: &img, origin: (0, 0) };
|
||||
let (cx, cy) = ((ox + ow / 2) as f32, (oy + oh / 2) as f32);
|
||||
let reg = grabcut::object_at(&s, canvas, cx, cy, grabcut::OBJECT_AT_WORK_PX);
|
||||
let reg = reg.unwrap_or_else(|| panic!("the {ow}×{oh} object is not found"));
|
||||
let v = iou(®, &truth, w, h);
|
||||
assert!(v > 0.8, "{ow}×{oh}: IoU {v}");
|
||||
}
|
||||
// The flat background around the small object is still nothing.
|
||||
let img = RgbImage::from_fn(w, h, |x, y| if (188..212).contains(&x) && (138..162).contains(&y) { [0.8, 0.1, 0.1] } else { [0.9, 0.9, 0.9] });
|
||||
let s = ImageSampler { img: &img, origin: (0, 0) };
|
||||
assert!(grabcut::object_at(&s, canvas, 400.0, 300.0, grabcut::OBJECT_AT_WORK_PX).is_none());
|
||||
}
|
||||
|
||||
// ---------- Select Subject on the cases reported in #2788 ----------
|
||||
|
||||
/// A figure whose upper part is close to the background colour (a cream shirt on a near-white
|
||||
|
||||
Reference in New Issue
Block a user