Model reference · open weights
UniverSat is an open-weight embedding model from g-astruc. AxForge deploys and operates it for you on dedicated EU-owned hardware — with the licence handled where one is required.
Available as managed deployment — configured and operated for you on dedicated EU hardware, quoted per deployment.
What it is
| Released by | g-astruc |
|---|---|
| Type | Embedding models |
| Task | Image embed |
| Parameters (lead) | 201M |
| Released | 2026-06-19 |
| Popularity | 515 downloads / month |
| Licence | Open weights |
About
One set of weights for many sensors, resolutions, scales, and modalities.
UniverSat is a ViT-style Earth Observation backbone built around a Universal Patch Encoder (UPE) that maps patches of arbitrary spatial, spectral, and temporal shape into a shared embedding space — no resampling, no channel selection, no per-sensor encoder. A single model is trained jointly on 13 sensors from 7 datasets spanning ~3 orders of magnitude in resolution, channel count, and revisit frequency, and generalises to unseen sensors within this gamut without input resampling.
The model is published with PyTorchModelHubMixin,
so from_pretrained pulls the weights (config.json + model.safetensors) straight from this repo:
from hubconf import UniverSat # from a local checkout on your path
model = UniverSat.from_pretrained("g-astruc/UniverSat").eval()
Equivalently, through Torch Hub — same weights, same tracked download, no local checkout needed:
import torch
model = torch.hub.load("gastruc/UniverSat", "from_pretrained").eval()
Loading requires huggingface_hub (and safetensors); building the model needs only torch.
model.encode(...) looks up per-modality wavelengths, physical resolution, and sub-patch factors
automatically from a built-in registry, so you only pass {modality_name: tensor}:
# Snapshot modalities: (B, C, H, W). Time series: (B, T, C, H, W) + a "_dates" tensor.
data = {
"spot": torch.randn(2, 3, 360, 360), # 1 m VHR RGB snapshot
"s2": torch.randn(2, 20, 10, 36, 36), # 10 m Sentinel-2 time series
"s2_dates": torch.randint(0, 365, (2, 20)), # day-of-year per timestamp
"s1": torch.randn(2, 12, 3, 36, 36), # 10 m Sentinel-1 (VV, VH, ratio)
"s1_dates": torch.randint(0, 365, (2, 12)),
"dsm": torch.randn(2, 1, 12, 12), # 30 m elevation snapshot
}
features, _ = model.encode(data, patch_size=40, output_grid=36)
# features: (2, 1296, 768) -> a 36×36 dense feature grid (register tokens stripped for you)
patch_size — patch size in metres (patch_size=40 → 40 m patches; scale = patch_size / 10 internally).output_grid — side G of the output grid (a G×G map, G² tokens), decoupled from the input patch size. The same model + inputs produce coarse or per-pixel maps just by changing it:patch, _ = model.encode(data, patch_size=40, output_grid=9) # 9×9 patch-level
dense, _ = model.encode(data, patch_size=40, output_grid=36) # 36×36 dense
highres, _ = model.encode(data, patch_size=40, output_grid=180) # 180×180 high-res
Unseen sensors? Pass the sensor's
wavelengths={...}(optical/hyperspectral), polarization codes (SAR),input_res={...}, andsubpatches={...}overrides toencode(...). The UPE uses these as positional encodings — no retraining needed.
Inputs should be normalised (per-channel z-score). For the low-level forward(...) API (explicit
wavelengths, latent grid, masking), see hubconf.py.
The encoder accepts any combination of the registered modalities below (and more — see
modality_registry.py). Time-series
modalities take a 5-D tensor plus a "_dates" companion (day-of-year, Jan 1 = 0).
| Modality | Type | Channels | Resolution |
|---|---|---|---|
aerial / aerialflair | snapshot | RGB-NiR (4) | 0.2 m |
spot | snapshot | RGB (3) | 1 m |
spotRGBN | snapshot | RGB-NiR (4) | 1.6 m |
naip | snapshot | RGB-NiR (4) | 1.25 m |
rgbneon | snapshot | RGB (3) | 0.1 m |
dem / dsm / ndemneon | snapshot | DSM / nDEM (1–2) | 0.2–30 m |
s2 (Sentinel-2) | time series | 10 | 10 m |
s1 (Sentinel-1) | time series | VV, VH, ratio (3) | 10 m |
l7 / l8 (Landsat) | time series | 6 / 11 | 30 / 10 m |
alos (ALOS-2) | time series | HH, HV, ratio (3) | 30 m |
modis | time series | 7 | 250 m |
enmap / EO1 / neon | snapshot | hyperspectral | 30 m / 30 m / 1 m |
UniverSat is pre-trained self-supervised on 13 sensors from 7 datasets with a combination of latent multimodal masked modeling (LM³) and cross-modal contrastive learning under aggressive (~90%) masking across channels, time, space, and modalities.
| Dataset | Sensors used |
|---|---|
| FLAIR-Hub | SPOT 6/7 + aerial UHR + Sentinel-1 + Sentinel-2 + DSM + nDEM |
| PASTIS-HD | SPOT 6/7 + Sentinel-1 + Sentinel-2 time series |
| TreeSatAI-TS | aerial UHR + Sentinel-1 + Sentinel-2 time series |
| Planted | Sentinel-1 + Sentinel-2 + Landsat-7/8/9 + ALOS-2 + MODIS |
| S2NAIP-Urban | NAIP + Landsat-8 + Sentinel-1 + Sentinel-2 |
| HyperGlobal | EO-1 Hyperion (175 bands) + Gaofen-5 (150 bands) |
| EarthView (NEON) | NEON RGB/UAV + NIS hyperspectral (396 bands) + nDEM |
Combined coverage: spatial resolution 0.1–300 m, temporal depth 1–150 images/year, spectral width 1–396 channels. Fold 1 of PASTIS
From the published model card. Full card on the HuggingFace links in the sidebar.
How it works
Using it via the API
Once AxForge deploys universat for you, it answers on the OpenAI-compatible API — the same base URL and keys as every other model. (universat below is illustrative; you get the exact model name on deployment.)
$ curl -sS https://api.axforge.ai/v1/embeddings \
-H "Authorization: Bearer $AXFORGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"universat","input":"text to embed"}'
Create an account — your API key is available in the console. 3M free tokens every 30 days with every new account.