Skip to content

Export

Every writer records the disp-xr version, package versions, input granules and the history of operations in the output's attributes. to_geotiff writes Cloud Optimized GeoTIFFs; pass it the Dataset and a layer to keep that provenance.

to_zarr

to_zarr(ds: Dataset, store: Union[str, Path, Any], *, preset: Preset = 'timeseries', overwrite: bool = False, mode: Optional[Literal['w', 'w-']] = None, keep_bits: Optional[int] = None, compression_level: int = 3, target_mb: Optional[float] = None, consolidated: bool = True, storage_options: Optional[Mapping[str, Any]] = None, group: Optional[str] = None) -> None

Write ds to a Zarr v3 store.

Parameters:

Name Type Description Default
ds Dataset

DISP dataset with (time, y, x) and (y, x) variables.

required
store str, Path or zarr store

Local path, URL (with storage_options) or any zarr Store.

required
preset ('timeseries', 'maps', 'ministack', 'whole', 'tiles')

Chunk layout; see the module docstring.

"timeseries"
overwrite bool

Replace an existing store (or group); by default it is refused.

False
mode ('w-', 'w')

Deprecated: use overwrite.

"w-"
keep_bits int

Mantissa bits to keep in float variables (lossy). None keeps the data exact, as Cal/Val outputs need.

None
compression_level int

Zstd level (inside Blosc for float layers).

3
target_mb float

Shard size target; defaults to config["chunks.target_mb"].

None
consolidated bool

Write consolidated metadata (readers need not use it).

True
storage_options mapping

fsspec options for a URL store.

None
group str

Write into this group of the store instead of the root.

None

Examples:

>>> dxr.to_zarr(ds, "F11116.zarr")
>>> dxr.to_zarr(ds, "F11116_maps.zarr", preset="maps")
>>> dxr.to_zarr(
...     ds, "s3://bucket/F11116.zarr", storage_options={"anon": False}
... )

to_netcdf

to_netcdf(ds: Dataset, path: Union[str, Path], *, preset: Preset = 'timeseries', keep_bits: Optional[int] = None, compression_level: int = 4, target_mb: Optional[float] = None, overwrite: bool = False) -> None

Write ds to NetCDF-4 (h5netcdf) with zlib and the read-chunk layout.

NetCDF has no shards, so the HDF5 chunk is the read chunk of preset. An existing file is refused unless overwrite=True.

Examples:

>>> dxr.to_netcdf(ds, "F11116.nc")

to_geotiff

to_geotiff(obj: Union[DataArray, Dataset], path: Union[str, Path], *, layer: str = 'displacement', per_epoch: bool = False, compress: str = 'DEFLATE', overwrite: bool = False) -> list[Path]

Write a layer as a Cloud Optimized GeoTIFF (COG) with overviews.

The file is tiled with internal overviews (GDAL adds them above 512 pixels), so it can be read in parts over HTTP. Its metadata holds the provenance: DISP_XR_VERSION, SOFTWARE_VERSIONS, INPUT_GRANULES and HISTORY.

Parameters:

Name Type Description Default
obj DataArray or Dataset

A (y, x) layer with a CRS, or (time, y, x) with per_epoch; or a Dataset, from which layer is written. Pass the Dataset to keep its provenance: input granules and history live in Dataset attributes.

required
layer str

Layer to write when obj is a Dataset.

'displacement'
path str or Path

Output file. With per_epoch, one file per epoch named <stem>_<YYYYMMDD>.tif next to it.

required
per_epoch bool

Write each epoch of a (time, y, x) layer to its own file.

False
compress str

GDAL compression.

'DEFLATE'
overwrite bool

Replace existing files; by default any existing output is refused before anything is written.

False

Returns:

Type Description
list[Path]

Files written.

Raises:

Type Description
ValueError

If the layer has no CRS, has the wrong dims, or one epoch is larger than config["max_load_bytes"].

FileExistsError

If an output file exists and overwrite is not set.

Examples:

>>> dxr.to_geotiff(ds.isel(time=-1), "last.tif")
>>> dxr.to_geotiff(velocity, "velocity.tif", layer="velocity")
>>> dxr.to_geotiff(ds, "epochs.tif", per_epoch=True)

to_mintpy

to_mintpy(ds: Dataset, outdir: Union[str, Path], *, static: Optional[Dataset] = None, reliability_threshold: float = 0.9, overwrite: bool = False) -> dict[str, Path]

Write MintPy inputs from a chained DISP dataset.

Parameters:

Name Type Description Default
ds Dataset

Output of :func:disp_xr.chain (optionally corrected, masked and re-referenced): displacement relative to one reference date.

required
outdir str or Path

Output directory, created if needed.

required
static Dataset

Output of :func:disp_xr.open_static; writes geometryGeo.h5.

None
reliability_threshold float

Fraction of valid epochs for maskReliable.h5 (see :func:disp_xr.quality_summary).

0.9
overwrite bool

Replace existing files.

False

Returns:

Type Description
dict[str, Path]

File name stem to path, for each file written.

Raises:

Type Description
ValueError

If ds is not chained, or a file exists and overwrite is False.

Examples:

>>> static = dxr.open_static(ds)
>>> files = dxr.to_mintpy(ds, "mintpy/", static=static)
>>> files["timeseries"]

GeoZarr for bowser

GeoZarr stores that bowser opens directly (PRD C4, plan task T27.9).

to_geozarr(ds, store) writes a GeoZarr store: the multiscale pyramid of :func:disp_xr.to_pyramid (or one flat group), with CF and Zarr proj: / spatial: metadata on every level. profile="bowser" (the default) adds what bowser <https://github.com/opera-adt/bowser>_ uses to display the data, without changing anything a strict GeoZarr reader relies on:

  • display levels by factor 2 until the shorter side is under 256 cells (bowser picks the level whose pixel matches the tile pixel, so analysis factors such as 33 alone leave mid zooms reading 30 m);
  • bowser_uses_spatial_ref on every variable: true only for displacement and velocity. Otherwise bowser infers it from the units and subtracts a reference value from sigmas (meters) and inversion residuals (radians);
  • long_name on every variable (the colorbar title);
  • reference_lonlat from :func:disp_xr.rereference (a point reference), shown as the reference marker;
  • the line-of-sight attributes bowser reads (heading_angle_deg, incidence_angle_deg and los_enu_ground_to_satellite, near, center and far) from the static layers, when given;
  • bowser_histogram per epoch on level 0 (bins and percentiles), so the histogram panel does not read a full-resolution epoch per request;
  • float32 data, and chunks of one epoch by 256 x 256 in shards of 4 epochs by 1,024 x 1,024 (the "tiles" preset, bowser's tuned layout).

Checked against bowser ops (0a61c6a; zarr 3.1.1, xarray 2025.8, rioxarray 0.19) with tests/bowser_check.py: the per-level proj: and spatial: attributes, the multiscales transform, consolidated metadata, spatial_ref as a coordinate, integer masks with a _FillValue (decoded to NaN) and one-chunk small levels do not change what bowser reads or shows.

to_geozarr

to_geozarr(ds: Dataset, store: Union[str, Path, Any], *, profile: Profile = 'bowser', pyramid: bool = True, factors: Sequence[int] = (), static: Optional[Dataset] = None, look_side: Literal['right', 'left'] = 'right', histogram: bool = True, min_size: int = 256, overwrite: bool = False, mode: Optional[Literal['w-', 'w']] = None, storage_options: Optional[Mapping[str, Any]] = None, **downsample_kwargs: Any) -> Optional[xr.DataTree]

Write ds as a GeoZarr store, by default one that bowser opens.

Parameters:

Name Type Description Default
ds Dataset

Chained, re-referenced data with (time, y, x) layers.

required
store str, Path or zarr store

Where to write; give it a .zarr suffix for bowser.

required
profile ('bowser', 'standard')

"bowser" adds display levels, bowser attributes, histograms and the tile chunk layout (see the module docstring); "standard" writes the plain :func:disp_xr.to_pyramid layout.

"bowser"
pyramid bool

Write a multiscale pyramid (groups 0, 1, ...); otherwise one flat group at the root.

True
factors sequence of int

Analysis levels (for example 33, 67, 333) next to the display levels of the bowser profile; the only levels of the standard profile (default 33, 67, 333 there).

()
static Dataset

Static layers with los_east and los_north (:func:disp_xr.open_static), for the line-of-sight attributes.

None
look_side ('right', 'left')

Radar look direction, for the heading (Sentinel-1 looks right).

"right"
histogram bool

Store per-epoch histograms of the level-0 float layers (bowser profile).

True
min_size int

Smallest side, in cells, of a display level (bowser uses 256).

256
overwrite bool

Replace an existing store; by default it is refused.

False
mode ('w-', 'w')

Deprecated: use overwrite.

"w-"
storage_options mapping

fsspec options for a URL store.

None
**downsample_kwargs Any

Passed to :func:disp_xr.downsample (weights, ...).

{}

Returns:

Type Description
DataTree or None

The pyramid written, or None for a flat store.

Examples:

>>> dxr.to_geozarr(ds, "frame.zarr", static=dxr.open_static(ds))

Then bowser run --stack-file frame.zarr.

GeoZarr stores that bowser opens directly (PRD C4, plan task T27.9).

to_geozarr(ds, store) writes a GeoZarr store: the multiscale pyramid of :func:disp_xr.to_pyramid (or one flat group), with CF and Zarr proj: / spatial: metadata on every level. profile="bowser" (the default) adds what bowser <https://github.com/opera-adt/bowser>_ uses to display the data, without changing anything a strict GeoZarr reader relies on:

  • display levels by factor 2 until the shorter side is under 256 cells (bowser picks the level whose pixel matches the tile pixel, so analysis factors such as 33 alone leave mid zooms reading 30 m);
  • bowser_uses_spatial_ref on every variable: true only for displacement and velocity. Otherwise bowser infers it from the units and subtracts a reference value from sigmas (meters) and inversion residuals (radians);
  • long_name on every variable (the colorbar title);
  • reference_lonlat from :func:disp_xr.rereference (a point reference), shown as the reference marker;
  • the line-of-sight attributes bowser reads (heading_angle_deg, incidence_angle_deg and los_enu_ground_to_satellite, near, center and far) from the static layers, when given;
  • bowser_histogram per epoch on level 0 (bins and percentiles), so the histogram panel does not read a full-resolution epoch per request;
  • float32 data, and chunks of one epoch by 256 x 256 in shards of 4 epochs by 1,024 x 1,024 (the "tiles" preset, bowser's tuned layout).

Checked against bowser ops (0a61c6a; zarr 3.1.1, xarray 2025.8, rioxarray 0.19) with tests/bowser_check.py: the per-level proj: and spatial: attributes, the multiscales transform, consolidated metadata, spatial_ref as a coordinate, integer masks with a _FillValue (decoded to NaN) and one-chunk small levels do not change what bowser reads or shows.

display_factors

display_factors(shape: tuple[int, int], min_size: int = 256, max_levels: int = 6) -> list[int]

Return factor-2 display levels, as bowser's own converter builds them.

Factors 2, 4, 8, ... while the shorter side stays at least min_size cells, at most max_levels of them.

bowser_attrs

bowser_attrs(ds: Dataset) -> xr.Dataset

Return ds with float32 data and the variable attributes bowser reads.

reference_lonlat

reference_lonlat(ds: Dataset) -> Optional[list[float]]

Return [lon, lat] of a point reference recorded by rereference.

None when the data were referenced to a median or an area, or not at all.

los_attrs

los_attrs(static: Dataset, look_side: Literal['right', 'left'] = 'right') -> dict[str, Any]

Return bowser's line-of-sight attributes from the static layers.

Near, center and far are the 5th, 50th and 95th percentiles of the incidence angle; the ENU vectors are rebuilt from those incidences and the median azimuth. The ground-track heading is the azimuth of the ground-to-satellite vector plus 90 degrees for a right-looking radar (Sentinel-1), minus 90 for a left-looking one.

Parameters:

Name Type Description Default
static Dataset

los_east and los_north (and los_up), as from :func:disp_xr.open_static.

required
look_side ('right', 'left')

Radar look direction.

"right"

histogram_stats

histogram_stats(da: DataArray) -> list[dict[str, Any]]

Return bowser's per-epoch histogram entries for one variable.

One dict per epoch (one for a 2D layer) with 100 bins, the counts, the extremes and the 2/98, 16/84 and 2.275/97.725 percentiles, matching bowser's _compute_histogram_stats. Epochs are computed in parallel, each read once.

Multiscale pyramids

Multiscale pyramids of downsampled levels in Zarr (PRD C4, plan task T27.7).

A pyramid is one Zarr group whose children are the levels, as sibling groups 0 (the input, 30 m), 1, 2, ... (each from :func:disp_xr.downsample of level 0). Every level is self-contained: CF spatial_ref, the Zarr proj: and spatial: attributes with its own spatial:transform and spatial:shape, and the provenance attributes. The root carries the multiscales layout: per level its asset, derived_from, the relative transform (scale = factor, translation 0, since cell edges start at the first pixel edge), the absolute spatial:transform and spatial:shape, the resampling method, and the reduction of each variable under disp_xr:resampling.

The multiscales, spatial: and proj: conventions are at pilot maturity (multiscales v0.1, breaking changes expected before v1), so their ids and URLs live only in this module and the version is recorded in the root attributes. :func:open_pyramid finds levels from the layout, never from group names.

Conventions: https://github.com/zarr-conventions/multiscales (v0.1), https://github.com/zarr-conventions/spatial, https://github.com/zarr-conventions/proj.

build_overviews

build_overviews(ds: Dataset, factors: Sequence[int] = (33, 67, 333), **downsample_kwargs: Any) -> xr.DataTree

Return a pyramid of ds and its downsampled levels as a DataTree.

Parameters:

Name Type Description Default
ds Dataset

Chained, re-referenced data at full resolution; level 0.

required
factors sequence of int

Downsampling factors of levels 1, 2, ... relative to ds, increasing. Every level is computed from ds (a weighted median cannot be built from a coarser level).

(33, 67, 333)
**downsample_kwargs Any

Passed to :func:disp_xr.downsample (stat, weights, variogram, ...). layers defaults to every numeric layer of ds, including the quality layers used for the weights, so every level holds the variables of level 0 (plus the cell statistics).

{}

Returns:

Type Description
DataTree

Children "0", "1", ... and the multiscales attributes on the root; lazy if ds is.

Examples:

>>> tree = dxr.build_overviews(ds, factors=(33, 67, 333))
>>> dxr.to_pyramid(tree, "F11116_pyramid.zarr")

to_pyramid

to_pyramid(data: Union[Dataset, DataTree], store: Union[str, Path, Any], *, factors: Sequence[int] = (33, 67, 333), overwrite: bool = False, mode: Optional[Literal['w-', 'w']] = None, preset: Preset = 'timeseries', small_mb: Optional[float] = None, storage_options: Optional[Mapping[str, Any]] = None, **downsample_kwargs: Any) -> xr.DataTree

Write a multiscale pyramid to a Zarr v3 store.

Parameters:

Name Type Description Default
data Dataset or DataTree

Full-resolution data (levels are built with :func:build_overviews and factors), or a tree from :func:build_overviews.

required
store str, Path or zarr store

Where to write.

required
factors sequence of int

Downsampling factors when data is a Dataset.

(33, 67, 333)
overwrite bool

Replace an existing store; by default it is refused.

False
mode ('w-', 'w')

Deprecated: use overwrite.

"w-"
preset ('timeseries', 'maps', 'ministack', 'whole', 'tiles')

Chunk layout of level 0 and of levels too large for one chunk (see :func:disp_xr.to_zarr).

"timeseries"
small_mb float

Levels whose largest variable is at most this many MB are stored as one chunk over all of time (a 1 km frame is about 250 x 300 cells); default config["chunks.target_mb"].

None
storage_options mapping

fsspec options for a URL store.

None
**downsample_kwargs Any

Passed to :func:disp_xr.downsample when data is a Dataset.

{}

Returns:

Type Description
DataTree

The pyramid that was written.

Examples:

>>> dxr.to_pyramid(ds, "frame_pyramid.zarr", factors=(33, 67))
>>> tree = dxr.open_pyramid("frame_pyramid.zarr")

open_pyramid

open_pyramid(store: Union[str, Path, Any], *, storage_options: Optional[Mapping[str, Any]] = None) -> xr.DataTree

Open a pyramid written by :func:to_pyramid, lazily.

Levels are read in the order of the root multiscales layout, from each entry's asset; groups the layout does not list are ignored.

Returns:

Type Description
DataTree

One child per level, named by its asset, with the root attributes (multiscales and provenance).

Examples:

>>> tree = dxr.open_pyramid("F11116_pyramid.zarr")
>>> tree["1"].ds