Skip to content

Switch hosting to Source Coop and improve Zarr v3 support

Choose a tag to compare

@avsm avsm released this 26 Aug 08:35
· 9 commits to main since this release

v0.10.0 (2026-08-25)

This release moves all data hosting to the Source Cooperative and makes the Zarr pipeline work end to end against remote object stores such as source.coop

There are several new model variants availabile, see geotessera info. You can also browse them interactively via Zarr on https://tze.geotessera.org

╭─────────┬──────────────────────┬────────────────┬─────────────╮
│ Version │ Variant              │ Repository dir │ Status      │
├─────────┼──────────────────────┼────────────────┼─────────────┤
│ 1.0     │ vultr (default)      │ v1             │ available   │
│ 1.1     │ cambridge (default)  │ v1.1-cam       │ available   │
│ 1.1     │ dclimate             │ -              │ coming soon │
│ 2.0     │ 2B-L~beta1 (default) │ v2-2B-L~beta1  │ available   │
│ 2.0     │ 2B-L~beta2           │ v2-2B-L~beta2  │ available   │
╰─────────┴──────────────────────┴────────────────┴─────────────╯

Breaking Changes

  • All NPY downloads now come from the Source Cooperative, fronted by CloudFlare.
    Embeddings, landmasks and manifests are served from the public
    https://data.source.coop/tessera/tessera repository over HTTPS,
    replacing the retired tessera-embeddings AWS S3 bucket. Note that we
    will deprecate and shut down older hosting soon as we need the storage
    capacity back, so please upgrade to this new library soon. (@avsm)

  • botocore and awscrt are no longer required dependencies as urllib3
    is a new direct dependency. s3:// locations need the new optional s3
    extra (pip install 'geotessera[s3]'), which pulls in s3fs and
    botocore (@avsm).

  • Convention metadata now comes from zarr-cm, replacing geozarr-toolkit.
    Stores stamp spatial:/proj: at revision r3 and multiscales at r2. (@avsm)

  • sphinx and cram are no longer runtime dependencies.
    Sphinx moved to a docs extra (pip install geotessera[docs])
    and cram to the dev dependency group (@avsm)

  • The Zarr read API drops crs=. The store is UTM-native, so
    GeoTesseraZarr takes lon/lat and routes to the utm{NN} group holding
    the point, and the .tessera accessor takes eastings and northings in
    that zone's own CRS. (@avsm)

  • The unused TesseraTileTransform class and its geotessera.tile_transform
    module are removed; GeoTesseraZarr datasets carry plain coordinate
    arrays instead. (#305 @aneeshnaik)

New Features

  • zarr_store_url accepts version names: zarr_store_url("v2") resolves
    to the v2 default variant's store, and explicit store paths still pass
    through. (@avsm)

  • GeoTesseraZarr and open_zone accept a zarr.abc.store.Store as
    well as a URL, and the new public zarr_store(location) builds the
    default retrying store. The store can therefore be wrapped, for
    example in zarr's experimental CacheStore for a local cache, and
    passed in. (@avsm)

  • Zarr reads from http(s) stores go through obstore, which retries
    each request with exponential backoff and jitter — one dropped response
    from a busy data server costs a chunk, not the whole read. obstore
    is a new dependency. (@avsm)

  • Matryoshka depth reads. sample_points, read_region, iter_region
    and read_patch take depth= on stores that declare depth arrays
    (v2 onwards): depth=16 reads the 16-dimension embeddings_d16
    prefix for an eighth of the bytes, dequantised by the shared scales
    as usual. A store without the requested depth raises and lists what
    it has. (@avsm)

  • read_region_quantized on GeoTesseraZarr and the .tessera
    accessor returns the int8 window and its scales without dequantising,
    at a quarter of the bytes of read_region; combined with depth=16
    a window costs 1/32 of the full float32 mosaic. (@avsm)

  • A new iter_region(bbox, year, strip_rows=...) on GeoTesseraZarr and
    the .tessera accessor streams a region as row strips of dequantised
    pixels with their transforms, downloading the next strip while the
    caller works on the current one. (@avsm)

  • A new GeoTesseraZarr.read_patch(lon, lat, year, size_px) returns a
    (size_px, size_px, 128) patch centred on a point. A patch within one
    UTM zone is sliced from the native grid unresampled; one crossing a zone
    boundary is merged onto a patch-centred transverse Mercator grid with
    nearest-neighbour resampling, and dst_crs= pins any other output CRS.
    read_region now warns when its bbox crosses a zone boundary. (@avsm @aneeshnaik)

  • The hosted Zarr store is available at
    https://data.source.coop/tessera/tessera/zarr/v1. GeoTesseraZarr()
    streams from it with no downloads and no configuration (@avsm)

  • Seam-aware point reads for sample_at and sample_points.
    Tiles are 0.1 degrees and UTM zones 6 degrees, so round coordinates fall on
    tile edges and multiples of 6 fall on zone seams. Our Zarr wrapper now
    tries the neighbouring UTM zone within 0.1 degrees of a seam, and
    search_px accepts the nearest valid pixel within 1 pixel. (@avsm)

  • A new probe() on the store and on the .tessera accessor returns
    (embedding, status), the status one of valid, water, nodata or
    outside. Use it to tell open water from a location the store does not
    cover, which sample_at reports alike as NaN. It also rejects a point
    beyond the grid instead of snapping it to the nearest edge pixel.
    Reported by Srinivasan Keshav (@avsm)

  • Per-version default variant allows omitting the dataset-variant.
    --dataset-variant now selects the version's default variant (vultr
    for v1, cambridge for v1.1, 2B-L~beta1 for v2) so
    GeoTessera(dataset_version="v1.1") works.
    The known datasets are listed in the new "Known Datasets" table printed by
    geotessera info (@avsm)

  • geotessera-registry s3scan scans Source Cooperative to list
    embeddings. This allows manifests and landmask registries to be
    regenerated directly from the Source Cooperative repository. (@avsm)

  • zarr-init and zarr-fill take locations rather than paths, so a store on
    one S3 node can be filled from tiles on another with no local mirror. (@avsm)

  • zarr-fill --zones N is safe to run as one process per UTM zone against a
    shared store, and fills are stateless.
    --rewrite-existing-shards forces a rebuild, needed only when the tile
    inventory has grown. (@avsm)

  • Each zone group carries stretch statistics collected during the fill, at no
    extra I/O: exact mean and covariance sufficient statistics per (zone, year)
    plus a weighted 20,000-pixel sample for quantiles. (@avsm)

  • zarr-stretch --per-zone computes one stretch per UTM zone, and
    zarr-global-preview --blend-zone-stretch colours each chunk with a blend
    of its zone's stretch and its neighbour's, weighted by longitude, so zone
    boundaries stay seamless by construction. zarr-global-preview builds the
    EPSG:4326 RGB pyramid from a remote store. (@avsm)

  • zarr-init --matryoshka-depths 4,16 also stores the first N dimensions of
    every embedding as their own arrays, so a client can read a 4- or
    16-dimensional prefix without decoding all 128 bands. Depth arrays share
    the shard grid with embeddings and are dequantised by the same scales;
    storage overhead is about 16%. Requires matryoshka-ordered dimensions and
    is refused below v2, where a prefix would be an arbitrary slice.
    zarr-fill, zarr-extend and zarr-scan follow the store's declaration,
    and zarr-global-preview reads its colour bands from the shallowest array
    that holds them. (@avsm)

  • geotessera-registry zarr-scan inventories a store's shards without
    writing anything, classifying each as written, missing, or empty.
    geotessera-registry zarr-verify checks a store's contents against the
    source tiles it was built from: it samples random (year, tile) pairs,
    reads a pixel block from each source .npy pair and the same ground
    position from the store, and requires embeddings to round-trip exactly,
    scales to match. zarr-scan remains the coverage check. (@avsm)

  • geotessera-registry zarr-extend appends years to an existing store's
    time axis. Time is chunked one year per chunk, making this a metadata-only
    edit; years may only be appended, since inserting an earlier one would
    renumber every chunk. (@avsm)

  • geotessera-registry zarr-consolidate re-consolidates a store's root
    metadata after in-place changes and merges the per-zone ingestion
    registries into _registry.parquet — the single-writer step that finishes
    a parallel sweep. Accepts a remote store URL as well as a local path.
    (@avsm)

  • open_zone(lon=...) and GeoTesseraZarr.open_zone(lon=...) accept a
    whole-number longitude. lon=-3 previously raised TypeError. (@avsm)

  • New geotessera-registry s3sync subcommand for one incremental pass that
    rescans a dataset's npy tree, diffs against the manifest as of the last
    successful sync, publishes the fresh manifest, fills only the changed
    (zone, year) pairs of the Zarr store (rewriting shards that absorbed
    new tiles), and re-renders just the changed zones of the preview year's
    RGB pyramid. (@avsm)

Performance

  • sample_points reads all points through zarr's own concurrent
    pipeline, one coordinate-indexed read per UTM zone instead of one
    request per point — about 19x faster on scattered points, more when
    clustered. Only unwritten pixels and seam points retry per point.
    (@avsm)

  • Point sampling no longer rebuilds a pyproj transformer for every point.
    (@avsm)

Bug Fixes

  • A single-zone read_patch reads its window through zarr's own pipeline
    instead of a dask reindex that materialised whole shard chunks — a
    64 px patch dropped from minutes to seconds. Its two dask progress
    bars (scales, then embeddings) had suggested two UTM zones were being
    queried; they were not. Reported by Aneesh Naik. (@avsm)

  • The cross-zone read_patch CRS is returned as WKT with a descriptive
    name ("Tessera patch Transverse Mercator lon_0=...") rather than an
    anonymous PROJ.4 string. Single-zone patches keep returning the
    zone's EPSG code; no EPSG code exists for the patch-centred meridian.
    Reported by Aneesh Naik. (@avsm)

  • read_region and iter_region size their window from all four corners
    of the lon/lat bbox. Northing extremes sit on different corners as the
    UTM grid curves away from the central meridian, so the old two-corner
    window silently cropped a wide box by up to a couple of kilometres at
    the top and bottom. (@avsm)

  • coverage renders a single-source dataset in the website's multi-colour
    year palette again, keeping per-source tints for maps that overlay
    several versions or variants; the globe viewer shows the matching
    legend. (#330 @adpeace)

  • Out-of-range scales no longer poison the stretch statistics. (@avsm)

  • zarr-stretch refuses to persist a stretch that fails its own drift check
    unless --allow-drift. It previously warned and saved anyway, which is
    how a covariance known to be wrong reached every preview built from it.
    (@avsm)

  • Fills no longer deadlock against an object store: a forked worker
    inherited s3fs's cached event loop but not the thread running it, and
    waited forever on its first call. Workers now start with "spawn" and
    reset any inherited fsspec state. (@avsm)

  • zarr-global-preview no longer races itself creating the pyramid: zarr's
    create is check-then-write, so parallel zone sweeps died on "already
    exists" errors. Every creation step is now idempotent. (@avsm)

  • Zones at the antimeridian no longer claim the whole grid width. A shard
    straddling 180° samples corners near both -180 and +180, so the naive
    span claimed every chunk column at that latitude — utm60 enqueued 1.4M
    level-0 chunks to reproject 360 shards, and the coarsening pass rewrote
    16M chunk slots for utm01's 18.8k real chunks. Both now detect the wrap
    and split into two tight ranges; every other zone is bit-identical. (@avsm)

  • The reprojection work list comes from the footprints of the shards that
    exist rather than the zone's bounding rectangle, which at high latitude
    back-projects across most longitudes. (@avsm)

  • Pyramid levels keep their per-level spatial:shape and
    spatial:transform, which geozarr-toolkit's layout builder silently
    dropped. (@avsm)

  • Incremental fills no longer erase neighbouring tiles: a shard write
    replaces the whole shard, so touched shards are now rebuilt from every
    tile overlapping them. (@avsm)

  • Failed shards are no longer recorded as written, so re-running a fill
    retries exactly the unfinished work, and a fill with any failed shard
    reports an error. (@avsm)

  • geotessera-registry propagates exit status. Command return codes were
    discarded, so failures reported success to the shell. (@avsm)

  • Build bookkeeping no longer lives inside the store, which now contains
    only Zarr. Anything an older build left at the store root is still read
    so existing stores resume correctly, but nothing is written back. (@avsm)

Documentation

  • The geotessera package docstring now covers GeoTesseraZarr alongside the
    GeoTIFF export path; it previously described only the latter (@avsm)

Internal

  • The globe viewer HTML embedded in cli.py moved to
    geotessera/templates/globe.html, shipped as package data (@avsm)