Skip to content

Synthetic data

tutorial

Synthetic OPERA DISP granules with a known truth, for examples and tests.

make_disp_stack(directory) writes a small frame of granules that open like real ones, and returns the displacement they were built from, so a result can be checked against the truth. The gallery and the test suite use it.

The files copy the layout of real granules (see tests/data/disp_s1_layout.md): one 2D (y, x) layer per variable, chunked and gzip-compressed with shuffle, a length-1 time coordinate, a reference_time variable, a spatial_ref grid mapping, and the /corrections, /identification and /metadata groups. Each ministack's reference date is the last secondary date of the previous ministack, as in real frames.

Request a stack by variant name through :func:make_disp_stack.

make_gnss_source(ds, pixels, cache_dir) serves synthetic GNSS stations that follow the DISP displacement at given pixels, through the geepers source interface, so :func:disp_xr.gnss.load and :func:disp_xr.residuals.compare run without a network (gnss extra). vertical_los(ds) gives matching static layers.

VARIANTS module-attribute

VARIANTS = ('two_ministacks', 'three_ministacks', 'with_gaps', 'duplicate', 'mixed_dtype', 'nisar', 'two_utm_zones')

DispStack dataclass

Paths of a written synthetic stack and the truth behind them.

Examples:

>>> truth = make_disp_stack(folder)
>>> truth.cumulative.shape, truth.velocity
times instance-attribute
times: DatetimeIndex

All epochs, starting with the first reference date.

reference_times instance-attribute
reference_times: DatetimeIndex

Reference date of each secondary epoch (times[1:]).

cumulative instance-attribute
cumulative: ndarray

Displacement relative to times[0], shape (len(times), ny, nx).

velocity instance-attribute
velocity: ndarray

Linear velocity in m/yr, shape (ny, nx).

make_disp_stack

make_disp_stack(root: Path, variant: str = 'two_ministacks', *, epochs_per_ministack: int = 5, shape: tuple[int, int] = (64, 64), seed: int = 0) -> DispStack

Write a synthetic DISP-S1 stack under root and return its truth.

Parameters:

Name Type Description Default
root Path

Directory to write into (created if missing).

required
variant str

One of :data:VARIANTS:

  • two_ministacks: 2 ministacks, one reference change.
  • three_ministacks: 3 ministacks, two reference changes.
  • with_gaps: 2 ministacks with two secondaries of the first removed.
  • duplicate: two_ministacks plus an older-version copy of one granule (path in extra["duplicate"]).
  • mixed_dtype: the second ministack is written in the 2026 production style (float64 displacement, fixed time epoch).
  • nisar: two_ministacks as pre-release DISP-NISAR SAS output (<ref>_<sec>.nc names, NISAR title, version 0.4).
  • two_utm_zones: two adjacent frames, UTM 10N and 11N, in root/F11116 and root/F11117; the second stack is in extra["second_frame"].
'two_ministacks'
epochs_per_ministack int

Secondaries per ministack.

5
shape tuple[int, int]

(ny, nx) of every layer.

(64, 64)
seed int

Seed for the noise.

0

Returns:

Type Description
DispStack

Paths and truth.

Examples:

>>> import tempfile
>>> from pathlib import Path
>>> truth = make_disp_stack(Path(tempfile.mkdtemp()), "two_ministacks")
>>> len(truth.paths)
10

correlated_noise

correlated_noise(shape: tuple[int, int], *, correlation_range: float, sill: float = 1.0, nugget: float = 0.0, spacing: float = SPACING, seed: int = 0) -> np.ndarray

Return a Gaussian random field with a known exponential covariance.

The covariance is sill * exp(-3 h / correlation_range) (practical range correlation_range, the convention of :class:disp_xr.residuals.VariogramModel) plus nugget of white noise, so variogram and cell-sigma estimates can be checked against the truth. Exact, by circulant embedding on a grid twice the size.

Parameters:

Name Type Description Default
shape (int, int)

(ny, nx) in pixels.

required
correlation_range float

Practical range, in the unit of spacing.

required
sill float

Variance of the correlated part.

1.0
nugget float

Variance of the added white noise.

0.0
spacing float

Pixel spacing.

SPACING
seed int

Random seed.

0

Examples:

>>> field = correlated_noise((64, 64), correlation_range=300.0, seed=1)
>>> field.shape
(64, 64)

make_gnss_source

make_gnss_source(ds: Dataset, pixels: list[tuple[int, int]], cache_dir: str | Path, *, noise: float = 0.001, extra_rate: dict[int, float] | None = None, seed: int = 0)

Return a geepers GNSS source whose stations follow DISP at pixels.

Each station sits at a (row, col) pixel; its daily up motion is that pixel's displacement (interpolated to days) plus white noise, with east and north zero, so it matches DISP under a vertical line of sight (:func:vertical_los). Needs the gnss extra (geepers).

Parameters:

Name Type Description Default
ds Dataset

Chained, re-referenced DISP with a CRS.

required
pixels list of (int, int)

Station pixels; stations are named S000, S001...

required
cache_dir str or Path

geepers cache directory (nothing is downloaded).

required
noise float

Standard deviation of the daily noise and the station sigmas (m).

0.001
extra_rate dict

Station index to an added up rate (m/yr), to make an outlier.

None
seed int

Seed of the noise.

0

Returns:

Type Description
geepers source

Pass it as source= to :func:disp_xr.gnss.load.

Examples:

>>> src = make_gnss_source(ds, [(4, 4), (30, 30)], "cache")
>>> gps = dxr.gnss.load(ds, source=src)

vertical_los

vertical_los(ds: Dataset) -> xr.Dataset

Return static layers with a vertical line of sight on the grid of ds.

Examples:

>>> import numpy as np, xarray as xr
>>> ds = xr.Dataset(coords={"y": np.arange(3.0), "x": np.arange(4.0)})
>>> vertical_los(ds.rio.write_crs("EPSG:32611")).los_up.shape
(3, 4)