Design
Details on how topozarr turns an Xarray Dataset into a multiscale Zarr store, and
which knobs control memory and performance.
Plan / execute split
create_pyramid is lazy — no data
is written. It produces a Pyramid
holding:
- level_templates: per-level
xr.Datasets - encoding: chunk and shard sizes per variable per level.
- attrs: root metadata following the zarr-conventions multiscales, proj, and spatial specs.
There are two ways to materialize the plan:
Pyramid.write(default): level 0 is streamed from the source dataset, then each levelNis block-reduced from the already-written levelN - 1through the Rust kernel (topozarr_core.block_reduce), so the source is read exactly once regardless of the number of levels. Work runs on a local thread pool (not Dask). The rest of this document describes this path.Pyramid.as_datatree: returns a lazyxr.DataTree(levels coarsened viaxarray.coarsen) for Dask-distributed writes. You callto_zarryourself, passingpyramid.encoding.
Chunk and shard heuristics
Spatial dimensions aim for chunks of target_chunk_bytes (default
~500 KB, sized for web visualization).
Shards group chunks_per_shard chunks per spatial dimension (default 4, i.e.
4×4 = 16 chunks, ~8 MB). Shards are also the unit of work during generation:
larger shards mean fewer, bigger reads/writes and more memory per worker.
chunks_per_shard sets a shard byte budget as well. A spatial dimension can
only hold as many chunks as fit whole, so a small raster — or any sufficiently
coarse pyramid level — leaves part of that budget unspent. The remainder widens
non-spatial dimensions instead of being discarded, innermost first, bounded by
each dimension's extent.
Kernel semantics
topozarr_core.block_reduce
- methods:
mean,max,min,sum,nearest, exported astopozarr_core.METHODS— the single source the Python layer validatescreate_pyramid(method=...)against, so a topozarr paired with a core that lacks a method fails at plan time rather than mid-write - dtypes:
u8,u16,i16,i32,i64,f32,f64 - 1–4 dimensional arrays
- shape follows
xarray.coarsen(boundary="trim"): trailing partial windows are dropped; an axis smaller than its stride still yields one window skipna=Trueskips NaN and_FillValueelements; an all-missing window produces 0 forsum(matchingnansum) and the fill value (or NaN) formean/max/min- integer dtypes stay integer:
meantruncates toward zero (unlikexarray.coarsen, which promotes to float) nearestdecimates: each window emits its top-left cell, ignoringskipna/fill_value. Intended for categorical data (class codes, masks) where averaging invents values; corner-pick is exactly composable, so chained per-step decimation equals decimation from native resolution
Tuning knobs
| Knob | Where | Effect |
|---|---|---|
levels / factors |
create_pyramid |
number of levels, or explicit cumulative downsample factors (sparse pyramids) |
target_chunk_bytes |
create_pyramid |
chunk size on disk |
chunks_per_shard |
create_pyramid |
shard size = work unit; None disables sharding |
max_region_bytes |
Pyramid.write |
cap on level-0 region widening |
max_workers |
Pyramid.write |
thread pool size; None = RAM/CPU-derived |
keep_levels_in_memory |
Pyramid.write |
keep written levels in RAM to skip re-reads; None = auto when they fit |
progress |
Pyramid.write |
tqdm bar over written regions |