Skip to content

Repository files navigation

TESSERA v2 — pixel-wise Earth foundation model

Tip

Want TESSERA v2 embeddings without running inference yourself? Submit a v2 Embedding Pre-Request to reserve your region and join the early testers — we'll prioritize your area. v2 coverage is still rolling out, so if you need embeddings right now, request v1.1 embeddings instead.

Temporal Embeddings of Surface Spectra for Earth Representation and Analysis (TESSERA) [CVPR2026]

PyPI version License

Table of Contents

Learning about TESSERA

Introduction

Satellite remote sensing enables a wide range of downstream applications, including habitat mapping, carbon accounting, and strategies for conservation and sustainable land use. However, satellite time series are voluminous and often cloud-corrupted, making them challenging to use: the scientific community's ability to extract actionable insights is often constrained by the scarcity of labelled training datasets and the computational burden of processing temporal data. The key insight behind our work, due to Dr. Clement Atzberger is that forcing auto-encoder embeddings derived from two cloud-free random samples of satellite time series to align using Barlow Twins results in an embedding that represents the entire time series, including the missing observations.

This idea is the key behind TESSERA, an open foundation model that preserves per-pixel spectral-temporal signals in 128-dimensional latent representations at 10-meter resolution globally. It uses self-supervised learning to summarise petabytes of Earth observation data. We compare our work with state-of-the-art task-specific models and other foundation models in five diverse downstream tasks and find that TESSERA closely matches or outperforms these baselines. By preserving temporal phenological signals that are typically lost in conventional approaches, TESSERA enables new insights into ecosystem dynamics, agricultural food systems, and environmental change detection. Moreover, our open-source implementation supports reproducibility and extensibility, while the privacy-preserving design allows researchers to maintain data sovereignty.

To our knowledge, TESSERA is unprecedented in its ease of use, scale, and accuracy: no other foundation model provides analysis-ready outputs, is open, and provides global, annual coverage at 10m resolution using only spectral-temporal features at pixel level.

Here are some visualization results of the TESSERA representation map (using the first three channels as RGB):

repr_demo

Papers

Here are publications and preprints related to TESSERA, listed chronologically:

Presentations

License

TESSERA software is released under the standard MIT license. Embeddings and model weights are released under the CC0 license: essentially, they can be freely used for both commercial and non-commercial purposes. Although we do not legally require attribution, we do request it.

Using TESSERA

Accessing Embeddings using GeoTessera (recommended)

We have generated embeddings for the whole globe at 10m resolution for 2024. Additionally, for regions such as the United States and Europe, embeddings are available from 2017 to 2025. These can be downloaded and used for downstream applications, saving significant computational time and resources, using the GeoTessera library. We will progressively extending coverage backwards year by year until 2017. If you find that your region does not have embeddings for a specific year, you can go to here to submit an embedding request. The current coverage map is below:

TESSERA Users Group

Interested users are invited to join our Zulip discussion groups.

Creating Your Own Embeddings

If you would like to use our software to create your own embeddings, please follow the instructions below. Note that this is a comptuationally challenging task and you will need access to significant computational and storage resources.

Hardware Requirements

1. Storage Requirements

Running this pipeline requires substantial storage space. Although the pipeline cleans up some intermediate files after processing, the downloaded raw Sentinel-2 and Sentinel-1 files will still occupy considerable disk space. For example, processing a 100km×100km area from 2022 to output a TESSERA Representation map (10m resolution) requires at least 1TB of storage.

2. Memory Requirements

We use preprocessed data, initially from Microsoft Planetary Computer. However, the next generation of embeddings will use OPERA from ASF DAAC. In either case, most of the geo-preprocessing has been done. Still, we recommend having at least 128GB of RAM.

3. CPU and GPU

The pipeline has no strict requirements for CPU and GPU, but more CPU cores and more powerful GPUs can significantly speed up inference. When processing a 110km×110km area from 2022, our tests using a 128-core CPU and a single NVIDIA A30 GPU for inference (CPU and GPU each handling 50% of the inference) took approximately 10 hours to complete.

4. Operating System

For the data preprocessing pipeline, we support almost all Linux systems. For Windows, we recommend using WSL. We do not support MacOS at this point.

For the model inference part, we have only tested it on Linux and Windows WSL, and they are working.

Data Preprocessing

Overview

We strongly recommend that you quickly review the entire tutorial before running the pipeline.

In this step, we stack a full year of Sentinel-1 and Sentinel-2 data along the time dimension to generate a composite. For Sentinel-2, the composite shape is (T,H,W,B), where T is the number of valid observations in that year, and B is the number of bands (we selected 10 bands). For Sentinel-1, we extracted both ascending and descending orbit data. Taking the ascending orbit as an example, the composite shape is (T',H,W,B'), where T' is the number of valid ascending observations in that year, and B' is 2 because we only obtain VV and VH bands.

Sentinel-2 channel order in bands.npy. The 10 bands are stored in the order shown below. This is not the conventional Sentinel-2 wavelength-ascending order; it is the convention used during Tessera pretraining, and the released model checkpoints together with the S2_BAND_MEAN / S2_BAND_STD constants in tessera_infer*/src/datasets/ssl_dataset.py are all bound to this exact ordering. Do not reorder unless you are also retraining or reordering the input-projection weights of the checkpoint.

idx name S2 code
0 red B04
1 blue B02
2 green B03
3 nir B08
4 nir08 B8A
5 rededge1 B05
6 rededge2 B06
7 rededge3 B07
8 swir16 B11
9 swir22 B12

Sentinel-1 (sar_ascending.npy, sar_descending.npy) channels are [VV, VH].

We initially sourced Sentinel-1 and Sentinel-2 data from Microsoft's Planetary Computer:

The new generation of embeddings will use OPERA from ASF DAAC:

Currently, our pipeline only accepts TIFF format input. The resolution of the input ROI TIFF can vary (e.g., 30m), but the pipeline will always generate Sentinel-1 and Sentinel-2 outputs at the configured RESOLUTION (default 10m) while keeping the ROI extent/bounds identical. For valid ROI areas within the TIFF, the value is 1; otherwise, it's 0. If you only have a shapefile, that's fine too - we provide a convert_shp_to_tiff.py script.

Download Source Code

First, create an empty working directory:

mkdir tessera_project
cd tessera_project
git clone https://github.com/ucam-eo/tessera.git

For easier pipeline operation, we recommend placing the data output directory at the same level as tessera_infer and tessera_preprocessing:

tessera_project
 ┣ tessera_infer
 ┣ tessera_preprocessing
 ┣ my_data
   ┣ roi.shp (your shapefile)
   ┗ roi.tiff (we recommend generating this using convert_shp_to_tiff.py)

The roi.tiff can be generated using convert_shp_to_tiff.py located in tessera_preprocessing/convert_shp_to_tiff.py. To use it, simply specify the path to your shapefile in the main function, and it will output a TIFF with the same name in the same directory.

⚠️Notice: If your ROI is relatively large, for example 100 km × 100 km, we strongly recommend pre-splitting the TIFF into smaller sections no larger than 20 km × 20 km. Then process each small TIFF file sequentially in the pipeline. An excessively large ROI may cause issues with backend tile providers

Python Environment

We need some geographic processing packages (fortunately, we won't be using GDAL, as configuring the environment is a nightmare) and some machine learning packages (PyTorch, but you'll need to install this yourself since the hardware on each computer is different). We've put some common packages in requirements.txt, which you can install as follows:

pip install -r requirements.txt

Note: If you are in a managed environment, you may need to install a venv first, using

python3 -m venv venv
source venv/bin/activate

Script Configuration

First, navigate to the tessera_preprocessing folder:

cd tessera_preprocessing

Then edit the file s1_s2_downloader.sh to point to the ROI TIFF file, the output and temporary directories, and the data source:

# === Basic Configuration ===
INPUT_TIFF="/absolute/path/to/your/data_dir/roi.tiff"
OUT_DIR="/absolute/path/to/your/data_dir"

export TEMP_DIR="/absolute/path/to/your/temp_dir"     # Temporary file directory

mkdir -p "$OUT_DIR"

# Python environment path
PYTHON_ENV="/absolute/path/to/your/python_env/bin/python"

# === Sentinel-1 & Sentinel-2 Processing Configuration ===
YEAR=2022 # Range [2017-2025]
RESOLUTION=10.0  # Output resolution (meters). ROI TIFF can be any resolution; extent is preserved.

# === Data Source Configuration ===
# mpc: Microsoft Planetary Computer (sentinel-1-rtc, sentinel-2-l2a)
# aws: AWS Open Data backends (S1=OPERA RTC-S1 via ASF/CMR + ASF Earthdata Cloud COGs, S2=Earth-search Sentinel-2 L2A COGs)
DATA_SOURCE="mpc"   # choices: mpc/aws

Note: RESOLUTION controls output pixel size. The pipeline keeps the ROI bounds fixed and resamples the ROI mask into the output grid.

AWS Credentials (only needed when DATA_SOURCE="aws")

Sentinel-2 on Earth-search is public and does not require credentials.

Sentinel-1 OPERA RTC-S1 is accessed via ASF Earthdata Cloud (COG over HTTPS). You need an Earthdata Login token:

  • Create an Earthdata account: via NASA Earthdata Login.
  • Approve Application: After registering your account, you can go to the Applications tab and add Alaska Satellite Facility Data Access to the list of approved applications.
  • Obtain an EDL Bearer token / JWT by clicking Generate Token and store it locally (do not commit it).

Recommended (simple + explicit):

nano ~/.edl_bearer_token
# paste token, save+exit (Ctrl-O Enter, then Ctrl-X)
chmod 600 ~/.edl_bearer_token

The AWS S1 downloader will use this token to read COGs from ASF Earthdata Cloud.

If you want to retrieve temporary S3 credentials (advanced; usually not required for this pipeline), see ASF guidance:

  • https://cumulus.asf.alaska.edu/s3credentialsREADME

Below the above configuration, there are some additional configurations that you can modify according to your computer's performance.

First, give permission to s1_s2_downloader.sh:

chmod +x s1_s2_downloader.sh

Then, we can run:

bash s1_s2_downloader.sh

Due to network conditions, processing some tiles may time out. Our script includes sophisticated timeout management to avoid these issues. However, sometimes some tiles may still fail. Running the above command again usually resolves this.

If all Sentinel-1 and Sentinel-2 data are generated correctly, they can be stacked along the time dimension. For this step, we use two Rust-generated executables, making it very fast.

(Optional) Build s1_stack and s2_stack from source

The repository ships pre-built s1_stack and s2_stack binaries directly under tessera_preprocessing/ so most users do not need to compile anything. The full Rust source for both stackers lives next to them:

tessera_preprocessing
 ┣ rust_for_s1_stacking
 ┃  ┣ Cargo.toml
 ┃  ┣ Dockerfile
 ┃  ┗ src/main.rs
 ┣ rust_for_s2_stacking
 ┃  ┣ Cargo.toml
 ┃  ┣ Cargo.lock
 ┃  ┣ Dockerfile
 ┃  ┗ src/main.rs
 ┣ s1_stack          # pre-built binary
 ┗ s2_stack          # pre-built binary

Rebuild from source if you want to modify the stacker (e.g. change channel order, sampling logic) or need a binary for a different architecture.

Option A — Local Cargo build (fastest path; produces a dynamically linked binary):

# Requires rustc / cargo >= 1.70
cd tessera_preprocessing/rust_for_s1_stacking
cargo build --release
cp target/release/s1_stack ../s1_stack          # overwrite the shipped binary
chmod +x ../s1_stack

cd ../rust_for_s2_stacking
cargo build --release
# the s2 crate name is `process_tile_downstream_wo_json`
cp target/release/process_tile_downstream_wo_json ../s2_stack
chmod +x ../s2_stack

Option B — Docker build (matches the way the shipped binaries are produced; statically linked against musl, runs on any Linux x86_64):

# s1
cd tessera_preprocessing/rust_for_s1_stacking
docker build -t s1_stack .
docker create --name tmp_s1 s1_stack
docker cp tmp_s1:/s1_stack ../s1_stack
docker rm tmp_s1
chmod +x ../s1_stack

# s2
cd ../rust_for_s2_stacking
docker build -t s2_stack .
docker create --name tmp_s2 s2_stack
docker cp tmp_s2:/s2_stack ../s2_stack
docker rm tmp_s2
chmod +x ../s2_stack

Sanity-check the resulting binaries:

./s1_stack --help
./s2_stack --help

Important — do not casually change the S2 channel order. The BANDS constant in rust_for_s2_stacking/src/main.rs defines the output channel order in bands.npy. It is bound to the released model checkpoints and the hard-coded S2_BAND_MEAN / S2_BAND_STD constants in tessera_infer*/src/datasets/ssl_dataset.py. Reordering it without also retraining (or reordering the input-projection weights of the checkpoint) will silently corrupt inference.

Run the stacker

You can open s1_s2_stacker.sh and edit the following:

# === Basic Configuration ===
BASE_DIR="/absolute/path/to/your/data_dir"
OUT_DIR="${BASE_DIR}/data_processed"
DOWNSAMPLE_RATE=1

Normally, we don't modify DOWNSAMPLE_RATE, which keeps it from performing any downsampling during stacking. The BASE_DIR in the above snippet is the same as the OUT_DIR you modified in s1_s2_downloader.sh.

Similarly, give permission to s1_s2_stacker.sh:

chmod +x s1_s2_stacker.sh

Then you can execute the stacking:

bash s1_s2_stacker.sh

After success, you will get some .npy files in /absolute/path/to/your/data_dir/data_processed. Usually, these .npy files are quite large, so we will patchify them into smaller, more manageable units.

Execute:

python dpixel_retiler.py \
    --tiff_path /absolute/path/to/your/data_dir/roi.tif \
    --d_pixel_dir /absolute/path/to/your/data_dir/data_processed \
    --patch_size 500 \
    --out_dir /absolute/path/to/your/data_dir/retiled_d_pixel \
    --num_workers 16 \
    --overwrite \
    --block_size 2000

You can change the above patch_size and block_size yourself. The above configuration is a recommended configuration for a TIFF with a shape of (5000,5000) and a 10m resolution.

If the above code runs smoothly, you can get some subfolders in my_data/retiled_d_pixel.

Inference

Once preprocessing has produced your tiles, you run a TESSERA model over them to generate embeddings. TESSERA comes in several versions — start with Before you start (shared by all versions), pick one in Which version should I use?, follow that version's own subsection, then stitch the tiles into a single map.

Before you start

1. Check your preprocessed tiles. Every version reads the same tile directories produced by tessera_preprocessing. Confirm my_data/retiled_d_pixel contains per-tile subfolders:

retiled_d_pixel
 ┣ 0_3500_500_4000
 ┣ 0_4000_500_4500
 ┣ 0_4500_500_5000
 ┗ ...

and that each subfolder has these files:

0_3500_500_4000
 ┣ bands.npy
 ┣ doys.npy
 ┣ masks.npy
 ┣ roi.tiff
 ┣ sar_ascending.npy
 ┣ sar_ascending_doy.npy
 ┣ sar_descending.npy
 ┗ sar_descending_doy.npy

If these files are missing, revisit the Data Preprocessing step.

2. Install PyTorch. Inference only needs PyTorch (no GDAL/SNAP), so it is far simpler to set up than preprocessing. If you don't already have it, check your CUDA version with nvidia-smi, then install the matching build from https://pytorch.org/, for example:

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128

Which version should I use?

Version Code Weights Output Use it when
TESSERA v2 (recommended) tessera_infer_v2/ Hugging Face 128-d Matryoshka (16/32/64/128) fp32, optional int8 New projects — best quality, smallest models, flexible embedding size
TESSERA v1.1 tessera_infer_QAT/ Google Drive 128-d int8 + scales You need the model behind the current GeoTessera int8 embeddings, MPC or AWS
TESSERA v1.0 (QAT) tessera_infer_QAT/ Google Drive 128-d int8 + scales Reproducing the original int8 embedding product
TESSERA v1.0 (early) tessera_infer/ Google Drive 128-d fp32 Reproducing early fp32 results only

The four code directories are independent and their checkpoints are not interchangeable — a checkpoint only loads with the pipeline it belongs to. If in doubt, use v2.


TESSERA v2 (recommended)

TESSERA v2 is the current generation of the model — see the preprint, TESSERA v2: Scaling Pixel-wise Earth Foundation Models. Its inference code lives in tessera_infer_v2/.

Don't want to run inference yourself? You can ask us to generate v2 embeddings for your region instead. Submit a v2 Embedding Pre-Request and we'll prioritize your area and add you to the early testers. Note that v2 coverage is still being rolled out, so if you need embeddings right away, request v1.1 embeddings instead — they're available today.

v2 ships four compact pixel students plus the 2B teacher they were distilled from:

Model Parameters Output Hugging Face repository
Nano 1.07 M 128-d Matryoshka geotessera/TESSERA-V-2.0-2B-N
Small 7.11 M 128-d Matryoshka geotessera/TESSERA-V-2.0-2B-S
Medium (recommended) 21.03 M 128-d Matryoshka geotessera/TESSERA-V-2.0-2B-M
Large 43.83 M 128-d Matryoshka geotessera/TESSERA-V-2.0-2B-L
2B teacher 2,064,266,242 1024-d geotessera/TESSERA-V-2.0-2B-Teacher

The students emit Matryoshka embeddings: the first K dimensions are independently usable for K ∈ {16, 32, 64, 128}, so 16, 32 or 64 dimensions can be stored instead of 128 with no retraining and no second checkpoint. The 2B in each name records the teacher the model was distilled from.

The 2B teacher is not a deployment model — it evaluates 2.06 billion parameters per pixel, which makes tile-scale, let alone global-scale, embedding generation impractical on ordinary hardware. It is published so the distillation is reproducible. For real work, use one of the students.

Download the weights

v2 checkpoints are not stored in this repository; they are hosted on the Hugging Face Hub under geotessera and fetched on demand:

cd tessera_infer_v2
pip install -r requirements.txt

python download_weights.py --model medium        # the recommended default (84 MB)
python download_weights.py --model all-students  # nano + small + medium + large
python download_weights.py --model teacher       # 8.26 GB

They land in tessera_infer_v2/student/checkpoints/ and tessera_infer_v2/teacher/checkpoints/, which is where the inference script looks by default. A single file can also be pulled directly:

from huggingface_hub import hf_hub_download
ckpt = hf_hub_download("geotessera/TESSERA-V-2.0-2B-M", "ckpt/student_medium.pt")

Run v2 inference

tessera_infer_v2/infer_v2.py consumes the preprocessed tile directories described in Before you start:

cd tessera_infer_v2

# default student, fp32 128-d output, one .npy per tile
python infer_v2.py --model medium \
    --data-root my_data/retiled_d_pixel \
    --out-dir   my_data/embeddings_v2

# 16-d Matryoshka prefix stored as int8 + a float32 scale map
python infer_v2.py --model medium --dim 16 --int8 \
    --data-root my_data/retiled_d_pixel \
    --out-dir   my_data/embeddings_v2_d16

# the 2B teacher on a single tile (GPU strongly recommended)
python infer_v2.py --model teacher --bf16 --batch-pixels 512 \
    --tile    my_data/retiled_d_pixel/0_3500_500_4000 \
    --out-dir my_data/embeddings_v2_teacher

Or call the encoders directly:

import sys, torch
sys.path.insert(0, "tessera_infer_v2/student")

from model import load_model
from infer import encode_tile

model = load_model("tessera_infer_v2/student/checkpoints/student_medium.pt",
                   torch.device("cuda"))
emb = encode_tile(model, s2_bands, s2_doys, s2_masks=s2_masks,
                  s1_asc_bands=s1_asc, s1_asc_doys=s1_asc_doys,
                  s1_desc_bands=s1_desc, s1_desc_doys=s1_desc_doys,
                  device=torch.device("cuda"))    # -> (H, W, 128)
emb16 = emb[..., :16]                             # Matryoshka truncation

Two conventions that fail silently. The Sentinel-2 channel order is B04 B02 B03 B08 B8A B05 B06 B07 B11 B12, not ascending wavelength (data from tessera_preprocessing is already correct). And the students normalise Sentinel-1 ascending/descending with their own per-source statistics before merging, whereas the teacher merges them raw and applies a single pooled set — do not carry one normalisation across to the other model. Full details in tessera_infer_v2/README.md.

When inference finishes, jump to Stitch the tiles into a representation map.


TESSERA v1.1 (QAT, int8)

TESSERA v1.1 is a pretrained QAT model that improves on the v1.0 QAT checkpoint. Its code lives in tessera_infer_QAT/. Compared to v1.0, v1.1 brings:

  • Wider encoder (latent_dim=192, transformer d_model=768) and an MLP dim_reducer (Linear → LayerNorm → ReLU → Dropout → Linear) that outputs a 192-D representation; the first 128 dims are saved for downstream use.
  • All-observation inference. Unlike v1.0, which randomly samples a fixed 40 timesteps per pixel, v1.1 uses every valid observation per pixel. Observation counts are bucketised to a configurable list num_obs_checkpoints (default: every multiple of 8 from 8 to 256, i.e. [8, 16, 24, 32, ..., 248, 256]) so pixels sharing the same bucket can be batched together.
  • Per-modality S1 normalisation. S1 ascending and descending are normalised with their OWN per-band mean/std, then concatenated time-wise into a single merged S1 stream that feeds one S1 backbone (same two-backbone topology as v1.0).

Two data sources × two checkpoint flavours

v1.1 ships two checkpoints per preprocessing source × two flavours each:

  • encoder-only (~250 MB, default for inference) — contains exactly the weights consumed by the inference graph (s2_backbone, s1_backbone, dim_reducer). This is what you almost always want.
  • full (~10 GB) — encoder + projector + optimiser/scaler state. Only download this if you intend to fine-tune v1.1 on your own data.

Each checkpoint was trained with its own normalisation statistics, so you must pair the right checkpoint with the right data_source setting in the config (otherwise the input distribution is silently mis-shifted and embedding quality collapses):

Data source Encoder-only (recommended, ~221 MB) Full (fine-tune, ~10 GB) data_source value
Microsoft Planetary Computer (S2 L2A + S1 RTC) tessera_v1_1_mpc_encoder.pt tessera_v1_1_mpc_full.pt "mpc"
AWS Open Data (Earth-search S2 L2A + ASF OPERA RTC-S1) tessera_v1_1_aws_encoder.pt tessera_v1_1_aws_full.pt "aws"

AWS checkpoint update (2026-05-03). The AWS v1.1 checkpoint was retrained on data collected with the corrected preprocessing pipeline (see tessera_preprocessing/s2_fast_processor.py::harmonize_arr, which previously double-applied the PB-04.00 BOA_ADD_OFFSET on AWS / Earth-search Sentinel-2 data). The AWS row in tessera_infer_QAT/src/datasets/v1_1_norm_stats.py has been refreshed against the new pretraining distribution.

Per-source normalisation statistics are kept in tessera_infer_QAT/src/datasets/v1_1_norm_stats.py. The config field data_source (default "mpc") selects which set is used at inference time.

The inference loader uses strict=False and silently ignores keys that aren't part of the inference graph, so a full checkpoint will also work in the same command — it's just ~45× larger to download. (A HuggingFace mirror will follow once the Drive links stabilise.)

Download the weights

Download one (or more) of the four checkpoints above and place into tessera_infer_QAT/checkpoints:

tessera_infer_QAT
 ┣ checkpoints
 ┃   ┣ tessera_v1_1_mpc_encoder.pt          # MPC, encoder-only (default)
 ┃   ┣ tessera_v1_1_aws_encoder.pt          # AWS, encoder-only
 ┃   ┣ tessera_v1_1_mpc_full.pt             # MPC, full (fine-tune only)
 ┃   ┗ tessera_v1_1_aws_full.pt             # AWS, full (fine-tune only)
 ┣ configs
 ┃   ┗ v1_1_infer_config.py                 # set data_source = "mpc" or "aws"
 ┣ src
 ┃   ┣ infer_v1_1.py
 ┃   ┗ datasets
 ┃       └─ v1_1_norm_stats.py
 ┗ visualize_embedding_v1_1.py

Run v1.1 inference

Run inference on a single preprocessed tile:

cd tessera_infer_QAT

# MPC inference (default; encoder-only ckp is enough)
python src/infer_v1_1.py \
    --config          configs/v1_1_infer_config.py \
    --checkpoint_path checkpoints/tessera_v1_1_mpc_encoder.pt \
    --tile_path       /absolute/path/to/retiled_d_pixel/0_3500_500_4000 \
    --output_dir      /absolute/path/to/representation_retiled_v1_1

For AWS data, pass --data_source aws and use the AWS encoder ckp: --checkpoint_path checkpoints/tessera_v1_1_aws_encoder.pt.

Adjust num_obs_checkpoints to trade off between embedding quality and compute (fewer / smaller checkpoints = faster, more / larger = more temporal detail). For a Slurm cluster, see tessera_infer_QAT/infer_v1_1.slurm for a ready-to-edit template.

Output files (int8 + scales, with _emb128_ naming):

  • <prefix>_emb128_int8.npy — shape (H, W, 128), dtype int8
  • <prefix>_emb128_scales.npy — shape (H, W), dtype float32

Reconstruct fp32 embeddings with fp32 = int8.astype(np.float32) * scales[..., None]. A helper script visualize_embedding_v1_1.py dequantises the output and saves a first-3-dim RGB plus a PCA-3 RGB for quick visual inspection.

When inference finishes, jump to Stitch the tiles into a representation map.


TESSERA v1.0 (QAT, int8)

The original quantization-aware model — this is the checkpoint used to generate the int8 embeddings in the GeoTessera library. Its code lives in tessera_infer_QAT/.

Download the weights

Download the QAT checkpoint from Google Drive and place it in tessera_infer_QAT/checkpoints:

tessera_infer_QAT
 ┣ checkpoints
 ┃   ┗ best_model_fsdp_20250608_220648_QAT.pt
 ┣ configs
 ┗ src

The QAT pipeline outputs quantized embeddings as int8 + scales:

  • tile_name.npy: int8 embedding tensor, shape (H, W, 128)
  • tile_name_scales.npy: float32 scale map, shape (H, W)

Run v1.0 QAT inference

The QAT pipeline runs through its own batch script, tessera_infer_QAT/infer_all_tiles.sh:

cd tessera_infer_QAT
chmod +x infer_all_tiles.sh
bash infer_all_tiles.sh

Before running, edit these parameters in tessera_infer_QAT/infer_all_tiles.sh:

BASE_DATA_DIR="/absolute_path_to_your_data_dir"
export PYTHON_ENV="/absolute_path_to_your_python/bin/python"
CPU_GPU_SPLIT="1:1"  # CPU:GPU ratio, e.g. 1:0 or 0:1
CHECKPOINT_PATH="checkpoints/best_model_fsdp_20250608_220648_QAT.pt"

Notes:

  • QAT supports both CPU and GPU inference in one run (ratio-based split, same style as the v1.0 early runner below).
  • On CPU, AMX is automatically detected and enabled when available; if AMX is not available, it automatically falls back to default CPU inference.

When inference finishes, jump to Stitch the tiles into a representation map.


TESSERA v1.0 (early, float32)

The earliest public checkpoint. It natively generates float32 embeddings, so it is not the model used for the int8 embeddings in the GeoTessera library — use v1.0 QAT or v1.1 for those. Its code lives in tessera_infer/.

Download the weights

Download the model weights from Google Drive and place the .pt file in the tessera_infer/checkpoints directory:

tessera_infer
 ┗ checkpoints
     ┗ best_model_fsdp_20250427_084307.pt
 ┗ configs
 ┗ src

Configure the batch script

Inference runs through tessera_infer/infer_all_tiles.sh. You only need to edit a few parameters:

a. Base data directory:

BASE_DATA_DIR="your_data_directory"

This is your data storage folder, the same as BASE_DATA_DIR used in preprocessing, e.g., /maps/usr/tessera_project/my_data

b. Python environment:

export PYTHON_ENV="your_python_path"

Write the absolute path to your Python environment here, e.g., /home/user/anaconda3/envs/tessera_env/bin/python

c. CPU/GPU split:

CPU_GPU_SPLIT="1:1"  # Format: CPU:GPU ratio

The script supports simultaneous inference using both CPU and GPU. This ratio specifies the proportion of retiled_patches each device will handle. Default is 1:1 (even split). For GPU-only inference, set to 0:1.

d. CPU Related Settings

MAX_CONCURRENT_PROCESSES_CPU=20

Maximum number of CPU processes for tile inference. For example, if set to 20, it will process 20 tiles simultaneously.

AVAILABLE_CORES=$((TOTAL_CPU_CORES / 2)) # Use 50% of the cores

Number of CPU cores to use. Please modify this value if necessary to avoid consuming too many CPU resources!

e. GPU Related Settings:

MAX_CONCURRENT_PROCESSES_GPU=1

Maximum number of GPU processes for inference. If the system has only 1 GPU, set this to 1.

GPU_BATCH_SIZE=1024  # Larger for GPU, if this takes too much memory, reduce it

Number of samples to process at once during PyTorch inference. If this value consumes too much GPU memory or causes an OOM error on the GPU, please reduce it accordingly.

f. Other Settings There are other parameters available for configuration. Please adjust them as needed.

Run v1.0 early inference

Once everything is ready, navigate to the tessera_infer folder, make the script executable, and run it:

cd tessera_infer
chmod +x infer_all_tiles.sh
bash infer_all_tiles.sh

If successful, you should see logs like:

(base) zf281@daintree:/scratch/zf281/tessera_project/tessera_infer$ bash infer_all_tiles.sh
[INFO] Total CPU cores: 256, Using: 192
[INFO] CPU:GPU split ratio = 1:1 (total: 2)

==== SETUP DIRECTORIES ====
[SUCCESS] Created necessary directories

==== SCANNING TILES ====
[INFO] Tile directory: /scratch/zf281/jovana/retiled_d_pixel
[INFO] Output directory: /scratch/zf281/jovana/representation_retiled
[SUCCESS] Found 226 tiles total
[INFO] Sample tiles:
  - 0_3500_500_4000
  - 0_4000_500_4500
  - 0_4500_500_5000
  - ...

At the same time, a logs folder will be generated in the tessera_infer folder with more detailed logging for each CPU and GPU process.


Stitch the tiles into a representation map

This step is the same for every version. Inference usually takes a long time, depending on your ROI size and hardware performance. Once completed, you can find many .npy files in my_data/representation_retiled:

representation_retiled
 ┣ 0_3500_500_4000.npy
 ┣ 0_4000_500_4500.npy
 ┣ 0_4500_500_5000.npy
 ┣ 0_5000_500_5500.npy
 ┣ 0_5500_500_6000.npy
 ┣ 0_6000_500_6500.npy
 ┣ 0_6500_500_7000.npy
 ┣ 0_7000_500_7500.npy
 ┣ 1000_0_1500_500.npy
 ┣ 1000_1000_1500_1500.npy
 ┣ 1000_1500_1500_2000.npy
 ┣ 1000_2000_1500_2500.npy

The final step is to stitch them together using tessera_infer/stitch_tiled_representation.py:

python stitch_tiled_representation.py \
--d_pixel_retiled_path /path/to/d_pixel_retiled \
--representation_retiled_path /path/to/representation_retiled \
--downstream_tiff /path/to/downstream.tiff \
--out_dir /path/to/output_directory

For example:

python stitch_tiled_representation.py \
--d_pixel_retiled_path /maps/usr/tessera_project/my_data/d_pixel_retiled \
--representation_retiled_path /maps/usr/tessera_project/my_data/representation_retiled \
--downstream_tiff /maps/usr/tessera_project/my_data/downstream.tiff \
--out_dir /maps/usr/tessera_project/my_data

Finally, you'll get a stitched representation map in the my_data directory with the shape (H,W,C), where H and W match your initial roi.tiff and C is the embedding dimension of the version you ran. The representation map is a NumPy array. If you want to convert it to TIFF for viewing in software like QGIS, you can use the tessera_infer/convert_npy2tiff.py script. Just modify the main function with:

npy_path = "/maps/usr/tessera_project/my_data/stitched_representation.npy"  # Change to the actual npy file path
ref_tiff_path = "/maps/usr/tessera_project/my_data/roi.tiff"  # Change to the actual reference tiff file path
out_dir = "/maps/usr/tessera_project/my_data/"  # Change to the actual output directory

Downstream tasks

If you want to reproduce the downstream tasks in the paper, you can visit https://github.com/ucam-eo/tessera-downstream-task. There are many examples provided there.

Additional information

Team

Cambridge Faculty

Postdoc

  • James Ball

PhD

  • Madeleine Lisaius
  • Zhengpeng (Frank) Feng
  • Robin Young
  • Jovana Knezevic

Undergrad

  • Zejia Yang (Part II student, working with Frank Feng on MAE pretraining of spatial feature extractors)

Interns

  • Kenzy Soror (U. Waterloo, working with Robin Young)
  • Artyom Gabtraupov (U. Waterloo, working with Robin Young)
  • Gabriel Mahler (U. Cambridge, working with Anil Madhavapeddy and Silviu Petrovan on hedgehog habitats and tracking)
  • Leyu Pan (Imperial College, working with Frank Feng on text embeddings generated from OSM)

Collaborators

Visitors

  • Silja Sormunnen, Aalto University, Finland
  • Isabel Mansley (U. Edinburgh, working with David Coomes and Anil Madhavapeddy on habitat mapping in Scotland

Contact

Please direct your technical questions to Frank Feng (zf281@cam.ac.uk) or ask it on our Zulip forum. Non-technical questions can be sent to Prof. S. Keshav (sk818@cam.ac.uk).

Citation

If you use TESSERA in your research, please cite the relevant paper(s).

TESSERA v2 (arXiv:2607.03949):

@article{feng2026tesserav2,
  title={TESSERA v2: Scaling Pixel-wise Earth Foundation Models},
  author={Feng, Zhengpeng and Jaffer, Sadiq and Shokar, Ira and Knezevic, Jovana and Elvers, Mark and Atzberger, Clement and Young, Robin and Naik, Aneesh and Robinson, Niall and Blake, Andrew and others},
  journal={arXiv preprint arXiv:2607.03949},
  year={2026}
}

TESSERA (v1, CVPR 2026) (arXiv:2506.20380):

@inproceedings{feng2026tessera,
  title={Tessera: Temporal embeddings of surface spectra for earth representation and analysis},
  author={Feng, Zhengpeng and Atzberger, Clement and Jaffer, Sadiq and Knezevic, Jovana and Sormunen, Silja and Young, Robin and Lisaius, Madeline C and Immitzer, Markus and Jackson, Toby and Ball, James and others},
  booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition},
  pages={34818--34831},
  year={2026}
}

Acknowledgments

We would like to express our gratitude to UKRI, the Isambard AI supercomputer team at Bristol, and the DAWN supercomputer team at Cambridge, for their generous support in this project. We also acknowledge support from NVIDIA, AMD, Vultr, the Dirac High Performance Computing Facility, Microsoft AI For Good Lab, Dr. Robert Sansom, dClimate, and Amazon Web Services (AWS) under their AWS Open Data program (https://opendata.aws/). This work would not have been possible without their support, computational resources and technical assistance. 

Star History

Star History Chart

AUP

TESSERA Terms of Use and Ethical Guidelines

License

TESSERA data and embeddings are made available under the Creative Commons 0 International License CC-0. This means you are free to:

  • Share — copy and redistribute the material in any medium or format
  • Adapt — remix, transform, and build upon the material for any purpose, even commercially

Purpose and Intended Uses

TESSERA was developed to advance scientific research and support environmental monitoring, conservation, sustainable agriculture, and understanding of Earth systems. We designed this tool to enable:

  • Scientific research and education
  • Environmental monitoring and conservation
  • Agricultural and food security analysis
  • Climate change research and adaptation planning
  • Sustainable land use and resource management
  • Public interest applications that benefit society and the environment

Ethical Guidelines

While the CC0 license permits broad use, we strongly encourage users to consider the ethical implications of their work. These ethical guidelines are advisory and do not impose legally enforceable restrictions. We request that users:

Act Responsibly:

  • Consider privacy implications when analyzing specific locations
  • Respect the rights and dignity of affected communities
  • Be mindful of potential dual-use concerns

Be Transparent:

  • Accurately represent the data's characteristics (annual resolution, 10m spatial resolution)
  • Acknowledge limitations in your applications
  • Do not misrepresent TESSERA's capabilities

Support Positive Impact:

  • Consider how your work contributes to societal benefit
  • Engage with affected communities when appropriate
  • Share findings that advance public knowledge

Data Characteristics

Users should understand that TESSERA provides:

  • Annual temporal resolution — data represents yearly summaries, not real-time or high-frequency monitoring
  • 10-meter spatial resolution — suitable for landscape-scale analysis
  • Spectral-temporal embeddings — compressed representations, not raw imagery

Please accurately represent these characteristics in your work.

Community Standards

We encourage responsible use and welcome community feedback. If you have concerns about potential applications or suggestions for improving these guidelines, please contact us.

We reserve the right to update these guidelines based on community input and emerging considerations, though such updates do not retroactively affect the CC-0 license under which data is released.

Contact

For questions or feedback: Email sk818@cam.ac.uk


Last updated: February 25, 2026

About

[CVPR26] TESSERA is a foundation model that can process time-series satellite imagery for applications such as land classification and canopy height prediction. Developed at the University of Cambridge, it enables efficient extraction of temporal patterns from Earth observation

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages