Planetary-scale answers, unlocked.
A Hands-On Guide for Working with Large-Scale Spatial Data. Learn more.
Authors
AlphaEarth Foundations embeddings gave us an initial view of temporal change in our earlier post on agricultural dynamics, but that first pass also exposed a limitation: raw embedding distances do not live on a consistent scale across pixels or land-cover types. As a result, it is hard to compare how unusual a given year looks from place to place, even when the relative signal is informative.
In this follow-up, we ask a more targeted question: can we score each annual embedding by how much it stands out from the rest of its local time series? To do that, we compute leave-one-out (LOO) medoid scores in embedding space and apply robust z-score scaling through time for each pixel. This gives us a more stable and interpretable way to identify unusual years across a short annual record.
As in the earlier AlphaEarth Foundations agriculture post, the goal is not to present a definitive change-detection benchmark. Instead, we show a practical workflow for surfacing unusual temporal states in AEF embeddings. We run that workflow against the global AEF Zarr mosaic hosted on Source Cooperative, build a multiscale output for exploration, and inspect several examples around Boulder, Colorado.
For this follow-up we use the global AlphaEarth Foundations Zarr mosaic hosted on Source Cooperative. Thanks to Taylor Geospatial and Source Cooperative for making the dataset available in a form that is easy to access lazily in a single python function call!
The mosaic stores annual AEF embeddings at 10-meter resolution in EPSG:4326, with nine temporal snapshots from 2017 through 2025. Working from one global store keeps the workflow simple: we can subset the region of interest, compute scores, and publish a derived multiscale product without assembling separate local inputs.
import xarray as xr
ds = xr.open_zarr( "s3://us-west-2.opendata.source.coop/tge-labs/aef-mosaic/", consolidated=False, storage_options={"anon": True}, ) ds
/Users/len/code/wherobots-notebook-blogs/.pixi/envs/default/lib/python3.14/site-packages/zarr/core/group.py:3559: ZarrUserWarning: Object at README.md is not recognized as a component of a Zarr hierarchy. warnings.warn( /Users/len/code/wherobots-notebook-blogs/.pixi/envs/default/lib/python3.14/site-packages/zarr/core/group.py:3559: ZarrUserWarning: Object at .checkpoint is not recognized as a component of a Zarr hierarchy. warnings.warn(
<xarray.Dataset> Size: 4PB Dimensions: (time: 9, band: 64, y: 1859584, x: 4009984) Coordinates: * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 * band (band) object 512B 'A00' 'A01' 'A02' 'A03' ... 'A61' 'A62' 'A63' * y (y) float64 15MB 83.69 83.69 83.69 ... -83.36 -83.36 -83.36 * x (x) float64 32MB -180.0 -180.0 -180.0 ... 180.2 180.2 180.2 Data variables: embeddings (time, band, y, x) int8 4PB dask.array<chunksize=(1, 64, 256, 256), meta=np.ndarray> Attributes: (12/15) proj:code: EPSG:4326 spatial:dimensions: ['y', 'x'] spatial:transform: [8.983111749910169e-05, 0.0, -180.0, 0.0, -8.983... spatial:transform_type: affine spatial:bbox: [-180.0, -83.36280346631479, 180.2213438735178, ... spatial:shape: [1859584, 4009984] ... ... geoemb:model: https://developers.google.com/earth-engine/datas... geoemb:source_data: https://source.coop/tge-labs/aef/v1/annual/ geoemb:data_type: int8 geoemb:gsd: 8.983111749910169e-05 geoemb:quantization: {'method': 'signed_square', 'original_dtype': 'f... zarr_conventions: [{'uuid': 'f17cb550-5864-4468-aeb7-f3180cfb622f'...
array([2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025], dtype=int32)
array(['A00', 'A01', 'A02', 'A03', 'A04', 'A05', 'A06', 'A07', 'A08', 'A09', 'A10', 'A11', 'A12', 'A13', 'A14', 'A15', 'A16', 'A17', 'A18', 'A19', 'A20', 'A21', 'A22', 'A23', 'A24', 'A25', 'A26', 'A27', 'A28', 'A29', 'A30', 'A31', 'A32', 'A33', 'A34', 'A35', 'A36', 'A37', 'A38', 'A39', 'A40', 'A41', 'A42', 'A43', 'A44', 'A45', 'A46', 'A47', 'A48', 'A49', 'A50', 'A51', 'A52', 'A53', 'A54', 'A55', 'A56', 'A57', 'A58', 'A59', 'A60', 'A61', 'A62', 'A63'], dtype=object)
array([ 83.68566 , 83.685571, 83.685481, ..., -83.362579, -83.362669, -83.362759], shape=(1859584,))
array([-179.999955, -179.999865, -179.999775, ..., 180.221119, 180.221209, 180.221299], shape=(4009984,))
At first glance, 3.81 PiB and 1,024,049,664 chunks in S3 sounds impractically large. For this demo, though, a few details keep the workflow manageable:
RasterFlow is Wherobots’ serverless inference engine for Earth Observation data. It builds inference-ready mosaics from remote sensing data and runs distributed workflows at scale. This notebook uses two RasterFlow capabilities:
run_mosaics_change
build_zarr_multiscales
For this run, we call run_mosaics_change on the RasterFlow Remote client. The method takes an input Zarr mosaic and writes out a derived Zarr mosaic containing one or more temporal change scores.
Here we compute both difference-over-time and LOO medoid scores in AEF embedding space, with robust z-score normalization applied across time for each pixel. The snippet below matches the current remote-client API used for this Colorado example.
from affine import Affine from rasterio.transform import rowcol # construct a bounding box selector for Colorado transform = Affine(*ds.attrs["spatial:transform"]) row_min, col_min = rowcol(transform, -109.060253, 41.003444) row_max, col_max = rowcol(transform, -102.041524, 37.000232) selector = {"x": [col_min, col_max], "y": [row_min, row_max]} selector
{'x': [np.int32(789701), np.int32(867833)], 'y': [np.int32(475138), np.int32(519702)]}
from rasterflow_remote import DistanceMetricEnum, RasterflowClient, TemporalScoreMethodEnum client = RasterflowClient() result = client.run_mosaics_change( store="s3://us-west-2.opendata.source.coop/tge-labs/aef-mosaic/", score_methods=[ TemporalScoreMethodEnum.DIFFERENCE, TemporalScoreMethodEnum.LOO_MEDOID, ], distance_metric=DistanceMetricEnum.ALPHA_EARTH_COSINE, data_var="embeddings", robust_time_zscore=True, selector=selector, )
To measure how unusual a given annual embedding is relative to its temporal context, we use a leave-one-out medoid (LOO medoid) score in embedding space. This is well suited to the AEF setting here, where we only have nine annual observations and want a reference that is less sensitive to outliers than a simple average.
Given a time series of embeddings {e1,…,eT}\{e_1, \dots, e_T\}, the LOO medoid score at time tkt_k compares eke_k to a representative embedding computed from the remaining observations:
E−k={ej∣j≠k}. E_{-k} = \{e_j \mid j \neq k\}.
We define that representative as the medoid of E−kE_{-k}, i.e. the embedding that minimizes total distance to all others:
emedoid(−k)=argminei∈E−k∑ej∈E−kd(ei,ej). e_{\text{medoid}}^{(-k)} = \arg\min_{e_i \in E_{-k}} \sum_{e_j \in E_{-k}} d(e_i, e_j).
The LOO medoid score is then: LOOmedoid(tk)=d(ek,emedoid(−k)), \mathrm{LOO}_{\mathrm{medoid}}(t_k) = d\left(e_k, e_{\text{medoid}}^{(-k)}\right),
where d(⋅,⋅)d(\cdot, \cdot) is a distance metric (e.g., Euclidean or cosine distance).
Interpretation: LOOmedoid(tk)\mathrm{LOO}_{\mathrm{medoid}}(t_k) measures how far the embedding at time tkt_k is from the medoid of the remaining time series. Larger values indicate that the year-specific embedding is less consistent with the rest of the temporal sequence, suggesting a potential change or anomalous state with respect to all other years.
Using the workflow above, we compute two normalized change layers across the nine-year Colorado subset of the AEF mosaic: a difference-over-time score and a LOO medoid score.
The intermediate result below is a Zarr dataset. You may notice:
ds = xr.open_zarr( "s3://sandbox-wherobots-mosaics-tmp/rasterflow/rasterflow/development/rbz2h49wvpjt7h4nzq5g/76e986b947cc.zarr" ) ds
<xarray.Dataset> Size: 253GB Dimensions: (time: 9, band: 2, y: 44800, x: 78336) Coordinates: * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 * band (band) object 16B 'difference_alpha_earth_cosine_distance_rob... * y (y) float64 358kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 * x (x) float64 627kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 Data variables: embeddings (time, band, y, x) float32 253GB dask.array<chunksize=(1, 1, 256, 256), meta=np.ndarray> Attributes: (12/15) proj:code: EPSG:4326 spatial:dimensions: ['y', 'x'] spatial:transform: [8.983111749910169e-05, 0.0, -180.0, 0.0, -8.983... spatial:transform_type: affine spatial:bbox: [-180.0, -83.36280346631479, 180.2213438735178, ... spatial:shape: [1859584, 4009984] ... ... geoemb:model: https://developers.google.com/earth-engine/datas... geoemb:source_data: https://source.coop/tge-labs/aef/v1/annual/ geoemb:data_type: int8 geoemb:gsd: 8.983111749910169e-05 geoemb:quantization: {'method': 'signed_square', 'original_dtype': 'f... zarr_conventions: [{'uuid': 'f17cb550-5864-4468-aeb7-f3180cfb622f'...
array(['difference_alpha_earth_cosine_distance_robust_time_zscore', 'loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'], dtype=object)
array([41.003663, 41.003573, 41.003483, ..., 36.979498, 36.979408, 36.979318], shape=(44800,))
array([-109.077928, -109.077839, -109.077749, ..., -102.041188, -102.041098, -102.041008], shape=(78336,))
To make the result easier to explore, we run the build_zarr_multiscales function to create a multiscale Zarr mosaic optimized for interactive viewing and analysis, then publish it to a public bucket for anyone to explore.
xr.open_datatree("s3://wherobots-examples/rasterflow/mosaics/co-aef-change.zarr")
<xarray.DataTree> Group: / │ Attributes: │ zarr_conventions: [{'uuid': 'd35379db-88df-4056-af3a-620245f8e347'... │ multiscales: {'layout': [{'asset': '0', 'transform': {'scale'... │ proj:code: EPSG:4326 │ proj:wkt2: GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System... │ spatial:dimensions: ['y', 'x'] │ spatial:registration: pixel │ spatial:transform_type: affine │ spatial:shape: [44800, 78336] │ spatial:transform: [8.983111749216732e-05, 0.0, -109.07797340998921... │ spatial:bbox: [-109.07797340998921, 36.97927342911413, -102.04... ├── Group: /0 │ Dimensions: (time: 9, band: 2, y: 44800, x: 78336) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 358kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 │ * x (x) float64 627kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 253GB ... │ spatial_ref int32 4B ... ├── Group: /1 │ Dimensions: (time: 9, band: 2, y: 22400, x: 39168) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 179kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 │ * x (x) float64 313kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 63GB ... │ spatial_ref int32 4B ... ├── Group: /2 │ Dimensions: (time: 9, band: 2, y: 11200, x: 19584) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 90kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 │ * x (x) float64 157kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 16GB ... │ spatial_ref int32 4B ... ├── Group: /3 │ Dimensions: (time: 9, band: 2, y: 5600, x: 9792) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 45kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 │ * x (x) float64 78kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 4GB ... │ spatial_ref int32 4B ... ├── Group: /4 │ Dimensions: (time: 9, band: 2, y: 2800, x: 4896) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 22kB 41.0 41.0 41.0 41.0 ... 36.98 36.98 36.98 │ * x (x) float64 39kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 987MB ... │ spatial_ref int32 4B ... ├── Group: /5 │ Dimensions: (time: 9, band: 2, y: 1400, x: 2448) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 11kB 41.0 41.0 41.0 40.99 ... 36.99 36.98 36.98 │ * x (x) float64 20kB -109.1 -109.1 -109.1 ... -102.0 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 247MB ... │ spatial_ref int32 4B ... ├── Group: /6 │ Dimensions: (time: 9, band: 2, y: 700, x: 1224) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 6kB 41.0 41.0 40.99 40.98 ... 36.99 36.99 36.98 │ * x (x) float64 10kB -109.1 -109.1 -109.1 ... -102.1 -102.0 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 62MB ... │ spatial_ref int32 4B ... ├── Group: /7 │ Dimensions: (time: 9, band: 2, y: 350, x: 612) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 3kB 41.0 40.99 40.97 40.96 ... 37.01 37.0 36.99 │ * x (x) float64 5kB -109.1 -109.1 -109.0 ... -102.1 -102.1 -102.0 │ Data variables: │ embeddings (time, band, y, x) float32 15MB ... │ spatial_ref int32 4B ... ├── Group: /8 │ Dimensions: (time: 9, band: 2, y: 175, x: 306) │ Coordinates: │ * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 │ * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... │ * y (y) float64 1kB 40.99 40.97 40.95 40.92 ... 37.04 37.01 36.99 │ * x (x) float64 2kB -109.1 -109.0 -109.0 ... -102.1 -102.1 -102.1 │ Data variables: │ embeddings (time, band, y, x) float32 4MB ... │ spatial_ref int32 4B ... └── Group: /9 Dimensions: (time: 9, band: 2, y: 88, x: 153) Coordinates: * time (time) int32 36B 2017 2018 2019 2020 2021 2022 2023 2024 2025 * band (band) StringDType() 32B 'difference_alpha_earth_cosine_dist... * y (y) float64 704B 40.98 40.93 40.89 40.84 ... 37.07 37.03 36.98 * x (x) float64 1kB -109.1 -109.0 -109.0 ... -102.2 -102.1 -102.1 Data variables: embeddings (time, band, y, x) float32 969kB ... spatial_ref int32 4B ...
array(['difference_alpha_earth_cosine_distance_robust_time_zscore','loo_medoid_alpha_earth_cosine_distance_robust_time_zscore'],dtype=StringDType())
array([41.003663, 41.003573, 41.003483, ..., 36.979498, 36.979408, 36.979318],shape=(44800,))
array([-109.077928, -109.077839, -109.077749, ..., -102.041188, -102.041098,-102.041008], shape=(78336,))
[63170150400 values with dtype=float32]
[1 values with dtype=int32]
array([41.003618, 41.003438, 41.003258, ..., 36.979723, 36.979543, 36.979363],shape=(22400,))
array([-109.077884, -109.077704, -109.077524, ..., -102.041412, -102.041232,-102.041053], shape=(39168,))
[15792537600 values with dtype=float32]
array([41.003528, 41.003169, 41.002809, ..., 36.980172, 36.979812, 36.979453],shape=(11200,))
array([-109.077794, -109.077434, -109.077075, ..., -102.041861, -102.041502,-102.041143], shape=(19584,))
[3948134400 values with dtype=float32]
array([41.003348, 41.00263 , 41.001911, ..., 36.98107 , 36.980351, 36.979633],shape=(5600,))
array([-109.077614, -109.076895, -109.076177, ..., -102.04276 , -102.042041,-102.041322], shape=(9792,))
[987033600 values with dtype=float32]
array([41.002989, 41.001552, 41.000114, ..., 36.982867, 36.981429, 36.979992],shape=(2800,))
array([-109.077255, -109.075817, -109.07438 , ..., -102.044556, -102.043119,-102.041682], shape=(4896,))
[246758400 values with dtype=float32]
array([41.00227 , 40.999396, 40.996521, ..., 36.98646 , 36.983585, 36.980711],shape=(1400,))
array([-109.076536, -109.073662, -109.070787, ..., -102.048149, -102.045275,-102.0424 ], shape=(2448,))
[61689600 values with dtype=float32]
array([41.000833, 40.995084, 40.989335, ..., 36.993646, 36.987897, 36.982148],shape=(700,))
array([-109.075099, -109.06935 , -109.0636 , ..., -102.055336, -102.049587,-102.043838], shape=(1224,))
[15422400 values with dtype=float32]
array([40.997958, 40.98646 , 40.974962, ..., 37.008019, 36.996521, 36.985023],shape=(350,))
array([-109.072224, -109.060726, -109.049227, ..., -102.069709, -102.058211,-102.046712], shape=(612,))
[3855600 values with dtype=float32]
array([40.992209, 40.969212, 40.946216, 40.923219, 40.900222, 40.877225,40.854229, 40.831232, 40.808235, 40.785238, 40.762241, 40.739245,40.716248, 40.693251, 40.670254, 40.647258, 40.624261, 40.601264,40.578267, 40.555271, 40.532274, 40.509277, 40.48628 , 40.463283,40.440287, 40.41729 , 40.394293, 40.371296, 40.3483 , 40.325303,40.302306, 40.279309, 40.256313, 40.233316, 40.210319, 40.187322,40.164326, 40.141329, 40.118332, 40.095335, 40.072338, 40.049342,40.026345, 40.003348, 39.980351, 39.957355, 39.934358, 39.911361,39.888364, 39.865368, 39.842371, 39.819374, 39.796377, 39.773381,39.750384, 39.727387, 39.70439 , 39.681393, 39.658397, 39.6354 ,39.612403, 39.589406, 39.56641 , 39.543413, 39.520416, 39.497419,39.474423, 39.451426, 39.428429, 39.405432, 39.382435, 39.359439,39.336442, 39.313445, 39.290448, 39.267452, 39.244455, 39.221458,39.198461, 39.175465, 39.152468, 39.129471, 39.106474, 39.083478,39.060481, 39.037484, 39.014487, 38.99149 , 38.968494, 38.945497,38.9225 , 38.899503, 38.876507, 38.85351 , 38.830513, 38.807516,38.78452 , 38.761523, 38.738526, 38.715529, 38.692533, 38.669536,38.646539, 38.623542, 38.600545, 38.577549, 38.554552, 38.531555,38.508558, 38.485562, 38.462565, 38.439568, 38.416571, 38.393575,38.370578, 38.347581, 38.324584, 38.301587, 38.278591, 38.255594,38.232597, 38.2096 , 38.186604, 38.163607, 38.14061 , 38.117613,38.094617, 38.07162 , 38.048623, 38.025626, 38.00263 , 37.979633,37.956636, 37.933639, 37.910642, 37.887646, 37.864649, 37.841652,37.818655, 37.795659, 37.772662, 37.749665, 37.726668, 37.703672,37.680675, 37.657678, 37.634681, 37.611684, 37.588688, 37.565691,37.542694, 37.519697, 37.496701, 37.473704, 37.450707, 37.42771 ,37.404714, 37.381717, 37.35872 , 37.335723, 37.312727, 37.28973 ,37.266733, 37.243736, 37.220739, 37.197743, 37.174746, 37.151749,37.128752, 37.105756, 37.082759, 37.059762, 37.036765, 37.013769,36.990772])
array([-109.066475, -109.043478, -109.020481, ..., -102.098455, -102.075458,-102.052461], shape=(306,))
[963900 values with dtype=float32]
array([40.980711, 40.934717, 40.888724, 40.84273 , 40.796737, 40.750743,40.70475 , 40.658756, 40.612762, 40.566769, 40.520775, 40.474782,40.428788, 40.382795, 40.336801, 40.290808, 40.244814, 40.198821,40.152827, 40.106834, 40.06084 , 40.014847, 39.968853, 39.922859,39.876866, 39.830872, 39.784879, 39.738885, 39.692892, 39.646898,39.600905, 39.554911, 39.508918, 39.462924, 39.416931, 39.370937,39.324944, 39.27895 , 39.232957, 39.186963, 39.140969, 39.094976,39.048982, 39.002989, 38.956995, 38.911002, 38.865008, 38.819015,38.773021, 38.727028, 38.681034, 38.635041, 38.589047, 38.543054,38.49706 , 38.451066, 38.405073, 38.359079, 38.313086, 38.267092,38.221099, 38.175105, 38.129112, 38.083118, 38.037125, 37.991131,37.945138, 37.899144, 37.853151, 37.807157, 37.761163, 37.71517 ,37.669176, 37.623183, 37.577189, 37.531196, 37.485202, 37.439209,37.393215, 37.347222, 37.301228, 37.255235, 37.209241, 37.163248,37.117254, 37.07126 , 37.025267, 36.979273])
array([-109.054977, -109.008983, -108.96299 , -108.916996, -108.871003,-108.825009, -108.779015, -108.733022, -108.687028, -108.641035,-108.595041, -108.549048, -108.503054, -108.457061, -108.411067,-108.365074, -108.31908 , -108.273087, -108.227093, -108.1811 ,-108.135106, -108.089112, -108.043119, -107.997125, -107.951132,-107.905138, -107.859145, -107.813151, -107.767158, -107.721164,-107.675171, -107.629177, -107.583184, -107.53719 , -107.491197,-107.445203, -107.399209, -107.353216, -107.307222, -107.261229,-107.215235, -107.169242, -107.123248, -107.077255, -107.031261,-106.985268, -106.939274, -106.893281, -106.847287, -106.801294,-106.7553 , -106.709307, -106.663313, -106.617319, -106.571326,-106.525332, -106.479339, -106.433345, -106.387352, -106.341358,-106.295365, -106.249371, -106.203378, -106.157384, -106.111391,-106.065397, -106.019404, -105.97341 , -105.927416, -105.881423,-105.835429, -105.789436, -105.743442, -105.697449, -105.651455,-105.605462, -105.559468, -105.513475, -105.467481, -105.421488,-105.375494, -105.329501, -105.283507, -105.237513, -105.19152 ,-105.145526, -105.099533, -105.053539, -105.007546, -104.961552,-104.915559, -104.869565, -104.823572, -104.777578, -104.731585,-104.685591, -104.639598, -104.593604, -104.54761 , -104.501617,-104.455623, -104.40963 , -104.363636, -104.317643, -104.271649,-104.225656, -104.179662, -104.133669, -104.087675, -104.041682,-103.995688, -103.949695, -103.903701, -103.857708, -103.811714,-103.76572 , -103.719727, -103.673733, -103.62774 , -103.581746,-103.535753, -103.489759, -103.443766, -103.397772, -103.351779,-103.305785, -103.259792, -103.213798, -103.167805, -103.121811,-103.075817, -103.029824, -102.98383 , -102.937837, -102.891843,-102.84585 , -102.799856, -102.753863, -102.707869, -102.661876,-102.615882, -102.569889, -102.523895, -102.477902, -102.431908,-102.385914, -102.339921, -102.293927, -102.247934, -102.20194 ,-102.155947, -102.109953, -102.06396 ])
[242352 values with dtype=float32]
Using the multiscales convention and building on carbonplan’s zarr-layer, we can visualize the nine-year mosaic.
If the viewer below is not working, try fullscreen mode here.
The video below shows the normalized change scores around Boulder, Colorado. It is a quick qualitative pass rather than a formal validation exercise, but several patterns stand out more clearly once the scores are viewed over time.
Your browser does not support the video tag.
In these examples, the LOO medoid score appears to highlight several distinct forms of change, including new development, roof replacement, changing water conditions, shifts in turf or vegetation cover, and burn scars. Interpretation still depends on local context, but the scores are more constent across space and time than the raw embedding distances, which makes it easier to spot unusual temporal states in the AEF mosaic.
This follow-up suggests that leave-one-out medoid scoring, combined with robust time-wise normalization, is more useful for inspecting AEF change signal than raw distance alone. The normalization makes change magnitude more comparable across pixels, while the leave-one-out medoid highlights anomalous signal across the full observation window rather than only between adjacent time steps. This does not make AEF embeddings a complete change-detection product, but it does provide a clearer and more consistent way to identify which years look unusual at each pixel.
That matters because AEF operates at a spatial and temporal resolution where many changes are subtle, cumulative, or mixed with recurring seasonal variation. A normalized score does not solve interpretation by itself, but it makes the first-pass search for interesting change much more practical.
In this notebook, we used run_mosaics_change on the RasterFlow Remote client to compute change scores from the global AEF mosaic, built a multiscales derivative for browser-based exploration, and reviewed several examples around Boulder. Followup work may include using the embeddings to also help filter the object types of interest for taking yet another step closer towards a more complete change-detection product, especially where we have external labels or stronger domain priors.
If you are interested in this approach, reach out to the Wherobots team at support@wherobots.com.
How We Delivered “Fields of The World” with RasterFlow: A Planetary-Scale GeoAI Pipeline
See how we used RasterFlow to run a 100TB+ global GeoAI pipeline, from feature mosaics to predictions and vectors, with reproducible workflows.
AlphaEarth Embeddings, Zonal Statistics, and PCA
Aggregate AlphaEarth embeddings over Iowa fields and visualize them with PCA.
Introducing the Wherobots Python SDK
What is the Wherobots Python SDK? The Wherobots Python SDK is a typed Python client for submitting, monitoring, and managing Wherobots job runs. It ships on PyPI as wherobots-python-sdk. One install, one API key, and you’re running spatial jobs from any Python environment: CI/CD pipelines, notebooks, a local shell. The SDK is built for three […]
Detecting Objects From Text Prompts with RasterFlow and Segment Anything 3
Exploring the capabilities of Segment Anything 3 on high-resolution Earth observation data.
share this article
Awesome that you’d like to share our articles. Where would you like to share it to: