Skip to content

Xarray Accessor

import zeit registers a .zeit accessor on every xarray.DataArray (and, to save or show results, on every xarray.Dataset). Its methods run the C++ algorithms over Dask chunks, so they work on cubes larger than memory and on clusters. On a dask-backed cube the algorithms are lazy: call .compute(), or write the result with save_raster or to_zarr_optimized. An in-memory cube gives an in-memory result.

Chunk in space, never in time

Each pixel needs its whole history, so keep time (and band, for CCDC) in a single chunk: cube.chunk({"time": -1, "y": 512, "x": 512}). See Parallel & Cloud Processing.

Method Input dims Output Parameters as in
landtrendr (time, y, x) xr.Dataset of vertices zeit.landtrendr
ccdc (time, band, y, x) xr.Dataset of segments zeit.ccdc
bfast_monitor (time, y, x) xr.Dataset of metrics zeit.bfast_monitor
bfast_lite (time, y, x) xr.Dataset of metrics zeit.bfast_lite
bfast (time, y, x) xr.Dataset of metrics zeit.bfast
mann_kendall (time, y, x) xr.Dataset of metrics zeit.mann_kendall
phenology (time, y, x) xr.Dataset of metrics, by year or season zeit.phenology
twdtw (time, y, x) or (time, band, y, x) xr.Dataset of classes and distances zeit.twdtw
smooth (time, ...) the smoothed DataArray zeit.smooth
snic (..., y, x) xr.Dataset of labels and means zeit.snic
som (..., y, x) xr.Dataset of clusters and prototypes zeit.som
tmask (time, band, y, x) clear (time, y, x) zeit.tmask
save any with y, x (also on a Dataset) writes a raster, returns its path zeit.save_raster
plot any with y, x (also on a Dataset) the viewer, a window or a figure zeit.plot
to_zarr_optimized any with y, x writes a Zarr store below

Each algorithm method returns an xr.Dataset with one variable per output, selected by name: result.slope, result["TRS5.sos"].

Change detection

landtrendr

DataArray.zeit.landtrendr(**kwargs)

LandTrendr on this (time, y, x) cube: the same as zeit.landtrendr(cube, **kwargs), with the same keyword arguments (direction, max_segments, nodata, fitted, …) and the same xarray.Dataset of vertices. The years come from the time coordinate. A dask-backed cube stays lazy and is computed block by block.

ndvi = ndvi.chunk({"time": -1, "y": 512, "x": 512})
lt = ndvi.zeit.landtrendr(max_segments=6)                  # direction="loss": NDVI drops
loss = zeit.extract_events(lt, min_magnitude=2000)         # still lazy
zeit.save_raster(loss, "results/")                         # computed while written

ccdc

DataArray.zeit.ccdc(**kwargs)

CCDC on this (time, band, y, x) cube of reflectance × 10,000: the same as zeit.ccdc(cube, **kwargs), with the same keyword arguments (qa, bands, max_segments, conseq_anom, …) and the same xarray.Dataset of segments. The dates come from the time coordinate. A dask-backed cube stays lazy and is computed block by block, with time and band in one chunk.

cube = cube.chunk({"time": -1, "band": -1, "y": 512, "x": 512})
segments = cube.zeit.ccdc(qa="fmask")       # QA band by name; still lazy
zeit.save_raster(segments, "ccdc_out/")      # computed while written

bfast_monitor

DataArray.zeit.bfast_monitor(monitor_start, **kwargs)

BFAST Monitor on this (time, y, x) cube: the same as zeit.bfast_monitor(cube, monitor_start, **kwargs), with the same keyword arguments (order, h, period, alpha, nodata, …) and the same xarray.Dataset of metrics. start_time and frequency come from the time coordinate unless given. A dask-backed cube stays lazy.

ndvi_16d = ndvi_16d.chunk({"time": -1, "y": 512, "x": 512})
bfm = ndvi_16d.zeit.bfast_monitor("2022-01-01")    # still lazy
zeit.save_raster(bfm, "results/bfm")               # computed while written

bfast_lite

DataArray.zeit.bfast_lite(**kwargs)

BFAST Lite on this (time, y, x) cube: the same as zeit.bfast_lite(cube, **kwargs).

bfast

DataArray.zeit.bfast(**kwargs)

Classic BFAST on this (time, y, x) cube: the same as zeit.bfast(cube, **kwargs).

Time-series analysis

mann_kendall

DataArray.zeit.mann_kendall(**kwargs)

Mann-Kendall test and Theil-Sen slope on this (time, y, x) cube: the same as zeit.mann_kendall(cube, **kwargs), with the same keyword arguments (method, alpha, lag, period, …) and the same xarray.Dataset of metrics.

mk = annual_ndvi.zeit.mann_kendall(method="hamed_rao")
slope = mk.slope.where(mk.h == 1)        # significant slopes only

phenology

DataArray.zeit.phenology(**kwargs)

Phenology on this (time, y, x) cube: the same as zeit.phenology(cube, **kwargs), with the same keyword arguments (curve, annual, max_seasons, weights, …) and the same xarray.Dataset of 21 metrics. The day numbering and the first year come from the time coordinate.

pheno = ndvi_16d.zeit.phenology(curve="beck", weights=weights)
sos = pheno["TRS5.sos"]                  # (year, y, x), day of year

twdtw

DataArray.zeit.twdtw(patterns, **kwargs)

TWDTW classification of this cube: the same as zeit.twdtw(cube, patterns, **kwargs), with the same keyword arguments (steepness, midpoint, cycle, max_elapsed, …) and the same xarray.Dataset. A dask-backed cube stays lazy.

classes = ndvi.zeit.twdtw({"soy": soy, "pasture": pasture})

smooth

DataArray.zeit.smooth(**kwargs)

Smoothing of this cube along time: the same as zeit.smooth(cube, **kwargs) (method, lmbda, weights, …).

smooth = ndvi.zeit.smooth(lmbda=100)

snic

DataArray.zeit.snic(**kwargs)

SNIC superpixels of this map or cube: the same as zeit.snic(da, **kwargs). The cube is loaded into memory.

seg = ndvi.zeit.snic(spacing=8, compactness=0.3)

som

DataArray.zeit.som(**kwargs)

Self-organizing map clusters of this map or cube: the same as zeit.som(da, **kwargs).

clusters = ndvi.zeit.som(x=3, y=3)

tmask

DataArray.zeit.tmask(**kwargs)

Tmask on this (time, band, y, x) cube: the same as zeit.tmask(cube, **kwargs).

clear = cube.zeit.tmask(green="green", swir="swir1")

Saving and showing results

The results of the algorithms are Datasets; .zeit.save and .zeit.plot write or show them (or any map or cube) without leaving a chain of calls:

zeit.landtrendr(ndvi).zeit.save("results/lt")               # one GeoTIFF per variable
zeit.extract_events(lt).yod.zeit.plot(basemap="satellite")  # one map
lt.zeit.plot()                                              # a selector of the variables

save

DataArray.zeit.save(path, **kwargs)
Dataset.zeit.save(path, **kwargs)

Writes this map, cube or result: the same as zeit.save_raster(data, path, **kwargs), with its keyword arguments (nodata, dtype, compress, driver, …). A Dataset with a path without extension becomes a folder with one GeoTIFF per variable; with .tif, one multiband raster. Returns the Path written. A lazy result is computed while it is written.

plot

DataArray.zeit.plot(**kwargs)
Dataset.zeit.plot(**kwargs)

Shows this map, cube or result: the same as zeit.plot(data, **kwargs), with its keyword arguments (var, fit, basemap, vector, static, save, …): the interactive viewer in a notebook, a window in a script, a figure with static=True or save=. A Dataset opens with a selector of its variables.

Storage

to_zarr_optimized

DataArray.zeit.to_zarr_optimized(
    store_path, chunk_size={'y': 512, 'x': 512},
)

Rechunks the array spatially and writes it to a Zarr store with consolidated metadata, locally or to object storage (s3://, gs://). With a Dask-backed array, this is the step that triggers the computation, and every worker writes its own chunks.

Parameter Type Default Description
store_path str required Local path or fsspec URL.
chunk_size dict {"y": 512, "x": 512} Chunk sizes of the stored array.
mk.slope.zeit.to_zarr_optimized("s3://my-bucket/ndvi_slope.zarr")