Skip to main content

sightloom_core/
tiling.rs

1//! Inference tiling helpers for large frames (4K/8K / small objects).
2//!
3//! Generates overlapping tile windows; hosts run detectors per tile and map
4//! boxes back with [`tile_to_global`].
5#![allow(clippy::cast_precision_loss)]
6
7#[cfg(feature = "alloc")]
8extern crate alloc;
9
10#[cfg(feature = "alloc")]
11use alloc::vec::Vec;
12
13use crate::{CoreError, Rect};
14
15/// One tile window inside a full-resolution frame.
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct TileWindow {
18    /// Tile column index.
19    pub col: u32,
20    /// Tile row index.
21    pub row: u32,
22    /// Left pixel (inclusive).
23    pub x0: u32,
24    /// Top pixel (inclusive).
25    pub y0: u32,
26    /// Tile width in pixels.
27    pub width: u32,
28    /// Tile height in pixels.
29    pub height: u32,
30}
31
32/// Generates overlapping tiles covering `frame_w` × `frame_h`.
33///
34/// `tile` is the tile edge length; `overlap` is the overlap in pixels
35/// (`0..tile`). Returns tiles in row-major order.
36///
37/// # Errors
38///
39/// Returns [`CoreError::InvalidThreshold`] when sizes are zero or overlap ≥ tile.
40pub fn generate_tiles(
41    frame_w: u32,
42    frame_h: u32,
43    tile: u32,
44    overlap: u32,
45) -> Result<Vec<TileWindow>, CoreError> {
46    if frame_w == 0 || frame_h == 0 || tile == 0 || overlap >= tile {
47        return Err(CoreError::InvalidThreshold);
48    }
49    let stride = tile - overlap;
50    let mut out = Vec::new();
51    let mut row = 0_u32;
52    let mut y = 0_u32;
53    loop {
54        let y0 = y.min(frame_h.saturating_sub(tile));
55        let height = tile.min(frame_h.saturating_sub(y0));
56        let mut col = 0_u32;
57        let mut x = 0_u32;
58        loop {
59            let x0 = x.min(frame_w.saturating_sub(tile));
60            let width = tile.min(frame_w.saturating_sub(x0));
61            out.push(TileWindow {
62                col,
63                row,
64                x0,
65                y0,
66                width,
67                height,
68            });
69            if x0 + width >= frame_w {
70                break;
71            }
72            x = x.saturating_add(stride);
73            col = col.saturating_add(1);
74        }
75        if y0 + height >= frame_h {
76            break;
77        }
78        y = y.saturating_add(stride);
79        row = row.saturating_add(1);
80    }
81    Ok(out)
82}
83
84/// Maps a box from tile-local coordinates to full-frame coordinates.
85///
86/// # Errors
87///
88/// Returns geometry errors from [`Rect::new`].
89pub fn tile_to_global(tile: TileWindow, local: Rect) -> Result<Rect, CoreError> {
90    let dx = tile.x0 as f32;
91    let dy = tile.y0 as f32;
92    Rect::new(
93        local.left() + dx,
94        local.top() + dy,
95        local.right() + dx,
96        local.bottom() + dy,
97    )
98    .map_err(|_| CoreError::NonFinite)
99}