Tiling and Caching¶
Processing pipelines need a consistent tile grid — the pyramid builder expects power-of-two aligned blocks, the warp engine needs manageable chunks, and display chains want to decode only what the viewport requires. Source images rarely cooperate: a TIFF may use 256x256 tiles or full-width strips, a JPEG 2000 file may encode the entire image as a single codestream block, and a NITF may tile at dimensions chosen for compression efficiency rather than processing convenience.
The toolkit provides a retiling adapter that presents any source as a uniform virtual tile grid, and a shared byte-budget cache that eliminates redundant decoding across operators.
TileCache¶
TileCache is a thread-safe LRU (least-recently-used) cache with a
byte budget. When the total size of cached arrays exceeds max_bytes,
the least-recently-accessed entries are evicted until the budget is
satisfied.
from aws.osml.image_processing import TileCache
cache = TileCache(max_bytes=512 * 1024**2) # 512 MiB budget
Eviction and Sizing¶
The cache is backed by cachetools.LRUCache configured with a
getsizeof function that reads the .nbytes property on each cached
numpy array. This means eviction decisions are based on the actual
memory footprint of each tile — a 3-band uint8 tile at 1024x1024
occupies 3 MiB, while a 4-band float32 tile at the same dimensions
occupies 16 MiB. Tiles whose nbytes exceeds max_bytes are silently
not cached (the cache cannot hold them regardless of eviction) — caching is an optimization, not a
correctness requirement.
Frozen Arrays¶
Arrays are marked read-only (writeable=False) on insertion. Cache
hits return the same array object (zero-copy), so multiple consumers
reading the same tile share one allocation. Consumers that need to
mutate a tile must call .copy() explicitly.
Why a Toolkit-Level Cache?¶
The codec layer (osml-imagery-io) is a Rust library with Python
bindings. Each call to get_block() decodes compressed data on the
native side and returns a new numpy array to the Python process.
Without caching, every repeated access to the same tile — whether from
overlapping retiling, the pyramid builder reading parent blocks, or a
warp engine hitting the same source region from adjacent output tiles —
pays the full cost of decompression and a Rust-to-Python memory copy.
TileCache stores decoded numpy arrays that live directly in the
Python process’s heap. Subsequent reads are a dict lookup and a
pointer return — no decompression, no cross-language data transfer.
This is especially significant for JPEG 2000 sources where
decompression is computationally expensive.
CachedImageProvider¶
CachedImageProvider is a transparent wrapper that interposes a
TileCache in front of any source’s get_block() method. It
delegates all properties (dimensions, bands, metadata) unchanged — the
rest of the pipeline cannot distinguish it from the unwrapped source.
When cache=None is passed, the wrapper becomes a no-op pass-through
with negligible overhead.
RetiledImageProvider¶
RetiledImageProvider presents a virtual tile grid of configurable
dimensions over any source. When the source’s physical blocks are
larger than the requested tile size, virtual tiles are sliced from
source blocks. When they are smaller, multiple source blocks are
stitched together to fill the virtual tile.
Parameter |
Default |
Description |
|---|---|---|
|
|
Virtual tile width in pixels |
|
|
Virtual tile height in pixels |
|
|
Pad edge tiles to full dimensions using the source’s pad pixel value |
|
|
Optional shared |