Process¶
Corrections and masks apply per granule, before chaining; re-referencing comes after. See Concepts.
Corrections¶
correct ¶
correct(ds: Dataset, which: Union[str, Sequence[str], None] = 'product', *, external: Optional[Mapping[str, DataArray]] = None, external_kind: Literal['differential', 'absolute'] = 'differential') -> xr.Dataset
Subtract correction layers from displacement.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Unchained DISP data opened with |
required |
which
|
"product", str or sequence of str
|
Product layers to subtract: |
'product'
|
external
|
mapping of str to xr.DataArray
|
Extra corrections in meters of LOS displacement, positive toward the
satellite like |
None
|
external_kind
|
('differential', 'absolute')
|
|
"differential"
|
Returns:
| Type | Description |
|---|---|
Dataset
|
|
Raises:
| Type | Description |
|---|---|
CorrectionError
|
If |
Examples:
remove_plate_motion ¶
Subtract rigid plate motion from displacement.
Each epoch loses v_los * (time - reference), where the reference is the
granule's reference_time before :func:disp_xr.chain and
attrs["reference_date"] after it, so it works on either. The result is in a
plate-fixed frame, comparable with plate-fixed GNSS.
Raises:
| Type | Description |
|---|---|
CorrectionError
|
If plate motion was already removed. |
Examples:
Quality¶
mask ¶
Set displacement to NaN where a pixel fails the quality criteria.
Only displacement and short_wavelength_displacement are masked;
quality layers are kept as they are. See :func:valid_mask for presets and
criteria. Every layer is per epoch, so each epoch is masked by its own
granule's quality. Works before or after chaining; the zero epoch that
:func:disp_xr.chain prepends has no quality layers and stays 0.
Examples:
valid_mask ¶
valid_mask(ds: Dataset, preset: Optional[str] = 'recommended', *, recommended: Optional[bool] = None, temporal_coherence: Optional[float] = None, phase_similarity: Optional[float] = None, connected_components: Optional[bool] = None, water: Optional[bool] = None) -> xr.DataArray
Return True where a pixel passes every chosen quality criterion.
Presets: "recommended" uses the product's recommended_mask;
"strict" adds connected components, water and temporal_coherence >=
the threshold stored on recommended_mask (0.6 if absent). Keyword
arguments add to or override the preset; False turns a criterion off.
A pixel at an integer layer's no-data value fails.
Raises:
| Type | Description |
|---|---|
ValueError
|
If no criterion is chosen, or a needed layer is not in |
Examples:
quality_summary ¶
quality_summary(ds: Dataset, *, reliability_threshold: float = 0.9, by_ministack: bool = False) -> xr.Dataset
Summarize quality per pixel over time (lazy).
Percentages divide by the epochs where the layer has data, so a stacked zero epoch or missing granules do not count as bad.
Returns:
| Type | Description |
|---|---|
Dataset
|
|
Examples:
quality_weights ¶
quality_weights(ds: Dataset, layers: Sequence[str] = DEFAULT_LAYERS, *, exponents: Optional[Mapping[str, float]] = None, floors: Optional[Mapping[str, float]] = None, penalty: float = 0.5, f0: float = 0.25, min_residual: float = 0.05) -> xr.Dataset
Combine quality layers into pixel weights between 0 and 1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
DISP data with the chosen layers (as from :func: |
required |
layers
|
sequence of str
|
Any of |
DEFAULT_LAYERS
|
exponents
|
mapping
|
Relative weight of each layer in the geometric mean (default 1 each). |
None
|
floors
|
mapping
|
Stretch floor per similarity or coherence layer. Default: the
processor's thresholds recorded on |
None
|
penalty
|
float
|
Residual weight factor per 2 pi cycle jump. |
0.5
|
f0
|
float
|
Scale of the residual weight's decay with the distance to the nearest whole cycle. |
0.25
|
min_residual
|
float
|
Lowest residual weight: cycle jumps down-weight, never exclude. |
0.05
|
Returns:
| Type | Description |
|---|---|
Dataset
|
|
Examples:
weights ¶
Quality weights from the DISP quality layers (PRD S2, plan task T26).
Each layer maps to a weight between 0 and 1, and the weights combine as a
weighted geometric mean, prod(w_i ** (a_i / sum(a))):
phase_similarity, temporal_coherence (good near 1)
Linear stretch clip((v - floor) / (1 - floor), 0, 1): values at or
below the floor get weight 0 and exclude the pixel at that epoch.
timeseries_inversion_residuals (good near 0)
The per-date sum of absolute residuals, in radians, of dolphin's L1
time-series inversion over every interferogram that includes the date.
An unwrapping error of one cycle in one interferogram adds about 2 pi to
both of its dates, so on real granules the residuals cluster near
multiples of 2 pi (residual_cycle_histogram). The residual splits
into cycles = round(r / 2 pi) and the distance to that lattice
frac = |r / 2 pi - cycles| (0 to 0.5). The weight is
penalty ** cycles * exp(-frac / f0), clamped to min_residual so a
cycle jump down-weights a pixel but never removes it (PRD decision). A
frac near 0.5 is ambiguous between two cycle counts and is flagged.
The per-epoch weight (time, y, x) combines the per-epoch layer weights;
the static weight (y, x) combines their time means, with the residual
term replaced by one minus the share of dates that have a cycle jump.
Defaults for the floors, penalty, f0 and min_residual are
provisional until the trade study (plan task T29).
residual_cycles ¶
Split inversion residuals into 2 pi cycles and the distance to them.
Returns:
| Type | Description |
|---|---|
Dataset
|
|
Examples:
network_degree ¶
Return the interferogram count of each epoch's date in its ministack.
In a bandwidth-limited network each date pairs with up to
max_bandwidth neighbours on each side within its ministack, whose
first date is the (compressed) reference. A secondary at position p
of N dates has min(p, b) + min(N - 1 - p, b) interferograms.
Examples:
max_bandwidth_from_granule ¶
Read interferogram_network.max_bandwidth from a granule's dolphin config.
Returns None if the config is missing or the network is not bandwidth-limited.
residual_cycle_histogram ¶
residual_cycle_histogram(ds: Dataset, bins: Optional[ndarray] = None, min_residual: float = 0.05) -> pd.Series
Histogram of the inversion residuals in units of 2 pi.
On real DISP-S1 granules the counts peak near whole numbers (one 2 pi
unwrapping error per interferogram), which the cycle decomposition of
:func:quality_weights assumes. Residuals below min_residual radians
(clean pixels) are left out so the peaks show.
Returns:
| Type | Description |
|---|---|
Series
|
Counts indexed by the lower bin edge, in cycles of 2 pi. |
Examples:
Chaining¶
chain ¶
chain(ds: Dataset, *, start: Optional[Union[str, datetime, Timestamp]] = None, end: Optional[Union[str, datetime, Timestamp]] = None, nan_policy: Union[str, NaNPolicy] = NaNPolicy.propagate, zero_epoch: bool = True) -> xr.Dataset
Chain ministacks so displacement is relative to one reference date.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Output of :func: |
required |
start
|
date - like
|
Chain from this ministack reference date; earlier ministacks are dropped. Must be one of the reference dates. |
None
|
end
|
date - like
|
Drop epochs after this date. |
None
|
nan_policy
|
('propagate', 'omit')
|
See :class: |
"propagate"
|
zero_epoch
|
bool
|
Prepend the reference date as an epoch with zero displacement. Other
layers get their no-data value there (NaN for floats, |
True
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Same variables; |
Raises:
| Type | Description |
|---|---|
MissingCrossoverError
|
If a reference date after the first is not an epoch of |
ValueError
|
If |
Examples:
NaNPolicy ¶
Bases: str, Enum
How a NaN at a crossover epoch affects later ministacks.
propagate (default): later epochs of that pixel become NaN, since their
offset is unknown. omit: the crossover's own displacement is taken as
zero, which assumes no motion over the missing interval, so later epochs
carry the offset accumulated before it.
Examples:
Spatial reference¶
rereference ¶
rereference(ds: Dataset, method: Union[str, ReferenceMethod] = 'auto', *, row: Optional[int] = None, col: Optional[int] = None, lon: Optional[float] = None, lat: Optional[float] = None, bbox: Optional[Sequence[float]] = None, geometry: Optional[Any] = None, border_pixels: int = 3, good_pixels: Optional[DataArray] = None, check_point: bool = True) -> xr.Dataset
Subtract a per-epoch reference value from the displacement layers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
DISP dataset, usually after :func: |
required |
method
|
str or ReferenceMethod
|
|
'auto'
|
row
|
int
|
Pixel for |
None
|
col
|
int
|
Pixel for |
None
|
lon
|
float
|
Point in degrees for |
None
|
lat
|
float
|
Point in degrees for |
None
|
bbox
|
sequence of float
|
|
None
|
geometry
|
shapely geometry
|
Polygon in lon/lat for |
None
|
border_pixels
|
int
|
Border width for |
3
|
good_pixels
|
xr.DataArray of bool
|
Pixels the medians may use, for example
|
None
|
check_point
|
bool
|
For |
True
|
Returns:
| Type | Description |
|---|---|
Dataset
|
|
Raises:
| Type | Description |
|---|---|
OutOfBoundsError
|
If the point is outside the grid or masked, or the area is empty. |
Examples:
Time series¶
fit_trajectory ¶
fit_trajectory(obj: Union[Dataset, DataArray], terms: Optional[Trajectory] = None, *, layer: str = 'displacement', weights: Optional[DataArray] = None, min_valid: int = 0, allow_denoised: bool = False) -> xr.Dataset
Fit a trajectory model to every pixel by ordinary least squares.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Dataset or DataArray
|
Chained displacement |
required |
terms
|
Trajectory
|
Model terms; default offset, velocity and an annual cycle. |
None
|
layer
|
str
|
Layer of a Dataset to fit. |
'displacement'
|
weights
|
DataArray
|
Per-epoch weights |
None
|
min_valid
|
int
|
Fewest valid epochs to fit a pixel; at least one more than the number of coefficients is always required. |
0
|
allow_denoised
|
bool
|
Fit data marked |
False
|
Returns:
| Type | Description |
|---|---|
Dataset
|
|
Examples:
Trajectory
dataclass
¶
Terms of a trajectory model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
polynomial
|
int
|
Degree: 0 offset only, 1 adds velocity, 2 adds acceleration (the
quadratic coefficient is |
1
|
periods
|
tuple of float
|
Periods in years of sine/cosine pairs: |
(1.0,)
|
steps
|
tuple of str
|
Dates of Heaviside steps (any pandas date string). |
()
|
postseismic_log
|
tuple of (str, float)
|
|
()
|
postseismic_exp
|
tuple of (str, float)
|
|
()
|
Examples:
predict_trajectory ¶
predict_trajectory(fit: Dataset, times: Union[ndarray, Sequence], dtype: str = 'float32') -> xr.DataArray
Evaluate a fitted trajectory at times (lazily if the fit is).
The model is rebuilt from the coefficient names and the reference time in
fit.attrs, so a fit read back from a store can be evaluated. Computed
in dtype and never stored unless you write it.
Examples:
Velocity for every pixel¶
Velocity for every pixel: OLS, Whittle colored noise, or MIDAS.
fit_velocity(ds, method=...) runs per pixel on a full frame:
"ols"
The linear term of :func:~disp_xr.timeseries.trajectory.fit_trajectory,
with white-noise sigmas (too small for real InSAR series).
"whittle"
The same OLS velocity, with noise estimated per pixel from the
periodogram of the OLS residuals (Whittle likelihood, power law plus
white): kappa, the white fraction and amplitudes, and the
colored-noise sigma of the velocity. Reliable from about 200 epochs; on
short records it underestimates |kappa| and the sigma
(benchmarks/whittle_bias.py). The sigma uses the covariance of the
complete record, so it is approximate for pixels with gaps, and the
amplitudes come from the gap-filled grid, which interpolation smooths
(about 10% low with 20% gaps). The exact grouped GLS is plan task T23.
"midas"
The median of slopes between epoch pairs about one year apart, trimmed at
two scaled MADs (Blewitt et al., 2016): robust to outliers, seasonal
signals and steps, with its own sigma. A step biases it when the pairs
that span it are a large share, about one year over the record length,
so prefer records of several years. Pairs are chosen from the stack
dates; a pair with a NaN end is dropped, where per-series MIDAS would
choose a replacement.
Blewitt, G., Kreemer, C., Hammond, W. C., & Gazeaux, J. (2016). MIDAS robust trend estimator for accurate GPS station velocities without step detection. JGR Solid Earth, 121, 2054-2068. https://doi.org/10.1002/2015JB012552
fit_velocity ¶
fit_velocity(obj: Union[Dataset, DataArray], method: Literal['ols', 'whittle', 'midas', 'gls'] = 'whittle', *, terms: Optional[Trajectory] = None, layer: str = 'displacement', min_valid: int = 0, noise: str = 'PLWN', groups: Union[str, DataArray] = 'coherence', n_groups: int = 5, n_samples: int = 16, allow_denoised: bool = False) -> xr.Dataset
Estimate a velocity and its sigma for every pixel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Dataset or DataArray
|
Chained displacement |
required |
method
|
('whittle', 'gls', 'ols', 'midas')
|
|
"whittle"
|
terms
|
Trajectory
|
Deterministic model for |
None
|
layer
|
str
|
Layer of a Dataset. |
'displacement'
|
min_valid
|
int
|
Fewest valid epochs to fit a pixel ( |
0
|
noise
|
('PLWN', 'FNWN', 'RWFNWN', 'WN', 'auto')
|
Noise model of |
"PLWN"
|
groups
|
'coherence' or DataArray
|
|
'coherence'
|
n_groups
|
int
|
Coherence bins, and pixels per group fitted to estimate its noise. |
5
|
n_samples
|
int
|
Coherence bins, and pixels per group fitted to estimate its noise. |
5
|
allow_denoised
|
bool
|
Fit data marked |
False
|
Returns:
| Type | Description |
|---|---|
Dataset
|
|
Examples:
gls ¶
Full-frame velocity by generalized least squares with colored noise (T23).
The noise covariance of a pixel depends on the epochs, which of them are
valid, and the noise parameters, not on the pixel's values. So the
covariance shape (the spectral index and the mix of noise components) is
estimated per group of pixels, by exact maximum likelihood
(:func:~disp_xr.timeseries.noise.fit_noise) on a sample of each group's
pixels, and every pixel is then fitted by exact GLS under its group's shape,
with its own noise amplitude profiled from its residuals. Each pixel gets a
colored-noise velocity sigma without an n x n factorization per pixel.
Groups are quantile bins of time-averaged temporal_coherence by default,
or any integer label map (for example quadtree cells, plan task T28).
Missing epochs. Each group's covariance over the complete record is inverted
once, Q = C⁻¹. For a pixel missing the epochs m, the inverse
covariance of its valid epochs is the Schur complement
P = Q - Q[:, m] Q[m, m]⁻¹ Q[m, :], which is zero on the missing rows and
columns. The normal equations and residual quadratic forms follow from
Q, Q A and a k x k inverse per pixel (k = len(m)), batched
over pixels with the same k.
NoiseShape
dataclass
¶
Covariance shape of a group: components (kappa, weight), weights sum to 1.
The covariance of a pixel is sigma2 * sum(w * C(kappa)) over the
components, with C(0) the identity and sigma2 the pixel's own
amplitude.
covariance ¶
Return the unit-amplitude covariance at the grid indices obs_idx.
coherence_groups ¶
Label pixels by quantile bin of their (time-mean) temporal_coherence.
estimate_group_noise ¶
estimate_group_noise(da: DataArray, labels: DataArray, *, terms: Trajectory, noise: str = 'PLWN', n_samples: int = 16, seed: int = 0, max_workers: Optional[int] = None) -> dict[int, NoiseShape]
Estimate a noise shape per group from a random sample of its pixels.
Each sampled pixel is fitted with :func:fit_noise (exact RMLE); the
group shape is the median of the samples' shapes. With noise="auto",
FNWN, PLWN and RWFNWN are fitted and the model with the lowest median BIC
is used for the group.
Model diagnostics¶
Per-pixel diagnostics of a fitted trajectory model (PRD S4, plan T32.6b-c).
:func:model_diagnostics describes the residuals of every pixel's fit: mean,
standard deviation, skewness, excess kurtosis, autocorrelation at chosen lags
and the Anderson-Darling statistic for normality. White-noise sigmas (the
sigma of :func:disp_xr.fit_trajectory) hold only when the residuals are
uncorrelated and Gaussian; autocorrelation above about 0.3 or an
Anderson-Darling statistic above about 0.75 (the 5% critical value) says
they are optimistic, and :func:disp_xr.fit_velocity with a colored-noise
method is the better tool. The model is evaluated on the fly from the
coefficients (:func:disp_xr.predict_trajectory), never stored, and every
statistic comes from one pass per block of pixels over time-contiguous
chunks.
:func:significance turns coefficients and sigmas into z-scores, two-sided
p-values and confidence intervals.
The statistics follow the Brisbane notebook (branch _stats_analysis.py),
with Anderson-Darling vectorized over pixels in NumPy instead of a
per-pixel apply_ufunc(vectorize=True).
model_diagnostics ¶
model_diagnostics(data: Union[Dataset, DataArray], fit: Dataset, *, layer: str = 'displacement', lags: Sequence[int] = (1,)) -> xr.Dataset
Describe the residuals of a trajectory fit at every pixel, lazily.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Dataset or DataArray
|
The data that were fitted, |
required |
fit
|
Dataset
|
:func: |
required |
layer
|
str
|
Layer of a Dataset. |
'displacement'
|
lags
|
sequence of int
|
Lags, in epochs, of the residual autocorrelation. |
(1,)
|
Returns:
| Type | Description |
|---|---|
Dataset
|
|
Examples:
significance ¶
Return z-scores, p-values and confidence intervals of fitted coefficients.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fit
|
Dataset
|
:func: |
required |
alpha
|
float
|
Confidence intervals cover |
0.05
|
Returns:
| Type | Description |
|---|---|
Dataset
|
|
Examples:
anderson_darling ¶
Return the Anderson-Darling normality statistic along axis, ignoring NaN.
Mean and variance are estimated from the data, with Stephens'
small-sample correction A² (1 + 0.75 / n + 2.25 / n²). Vectorized: one
sort for all series. NaN where fewer than 8 values are finite.
Examples:
Time grids, gaps and breaks¶
Regular time grids, refused gaps and declared breaks (PRD S3, plan task T30).
Rules from the temporal-denoising study (Kiritimati DISP-S1, 2026-09):
- Grid step. 15 d where sampling is genuinely 12-day, 30 d for 24-day
sampling. A 15-day grid is finer than 24-day sampling supports. The step is
chosen from the 75th percentile of the acquisition intervals rather than
the median: the study's
presegment has a 12-day median (18 of its 34 intervals) but 14 intervals of 24 days, and the study recommends 30 d for such frames. The step is 15 d per 12 d of that percentile, rounded. - Gaps are refused, not interpolated. A grid point farther than
max_gap_days / 2from every acquisition lies inside a hole longer thanmax_gap_daysand is marked; a 15-day grid acrosspre's 288-day hole would otherwise invent about 19 samples. - Breaks are declared (earthquake catalogs, user input); no automatic
detection before 1.0. A break at
tblands on the first epoch at or after it: the first observation that already contains the jump. Filters that know nothing about discontinuities respect them when run per segment (:func:apply_segmented).
regular_time_grid ¶
regular_time_grid(times: TimesLike, step: Optional[int] = None, *, start: Optional[Any] = None, end: Optional[Any] = None) -> pd.DatetimeIndex
Return a regular grid of dates covering the acquisitions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times
|
array of datetimes
|
Acquisition times, increasing. |
required |
step
|
int
|
Grid step in days; default :func: |
None
|
start
|
date - like
|
Grid bounds; default the first and last acquisition days. |
None
|
end
|
date - like
|
Grid bounds; default the first and last acquisition days. |
None
|
Returns:
| Type | Description |
|---|---|
DatetimeIndex
|
Midnight dates from |
Examples:
grid_step_days ¶
Return the grid step in days for these acquisitions (15 per 12 d of sampling).
Uses the 75th percentile of the intervals, so a frame with a mix of 12- and 24-day intervals gets 30 d (see the module docstring).
Examples:
gap_mask ¶
Return True for grid dates inside a refused gap.
A grid date farther than max_gap_days / 2 from every acquisition lies
in a hole longer than max_gap_days; resampled values there must be
NaN, never interpolated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times
|
array of datetimes
|
Acquisition times. |
required |
grid
|
array of datetimes
|
Grid dates (for example :func: |
required |
max_gap_days
|
float
|
Longest hole to bridge; default four times the typical (75th percentile) interval, so a missed acquisition or two is bridged and a season-long hole is not. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Boolean, one per grid date. |
Examples:
split_at_breaks ¶
apply_segmented ¶
apply_segmented(fn: Callable, data: Union[DataArray, Dataset, ndarray], breaks: Optional[Sequence[Any]], *, times: Optional[TimesLike] = None, min_len: int = 3) -> Any
Run fn on each segment between breaks independently.
A filter that knows nothing about discontinuities then never smooths across one, at the cost of one-sided support at each segment edge.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
callable
|
For xarray input, |
required |
data
|
(DataArray, Dataset or ndarray)
|
Series with |
required |
breaks
|
sequence of date-like
|
Break dates. |
required |
times
|
array of datetimes
|
Acquisition times for NumPy input (xarray uses its |
None
|
min_len
|
int
|
Segments shorter than this are passed through unchanged: one or two epochs carry nothing a smoother could use. |
3
|
Returns:
| Type | Description |
|---|---|
Same type as ``data``.
|
|
Examples:
Temporal denoising¶
Temporal denoising: gated Kalman smoother and robust LOWESS (PRD S3, T31).
Both methods share one interface, :func:denoise, and evaluate on the
acquisitions or on a regular grid (:func:disp_xr.timeseries.regular_time_grid)
with refused gaps. They are for display, resampling, linearity checks and
transient hunting: the temporal-denoising study (Kiritimati DISP-S1, 2026-09)
found that no filter improves a velocity, because InSAR residuals at 12-day
sampling are white and ordinary least squares is already optimal. Fit rates on
the raw series; :func:disp_xr.fit_trajectory refuses data marked as denoised.
Kalman (method="kalman"): a local-linear-trend model (displacement and
velocity, an integrated random walk) with a Rauch-Tung-Striebel smoother, or a
fixed-lag smoother for products that publish as data arrive. The only knobs
are the timescale tau_days (20 to 30 d), which sets the process noise
q = 3 R / tau³ against the measurement variance R (estimated per pixel
from the data), and the χ² gate: an update whose normalized innovation squared
exceeds gate (9) is skipped. A one-epoch outlier makes one huge innovation
and is rejected; a real transient builds over several epochs that stay inside
the gate. In the study it left 0.2 to 0.4% of an injected spike and kept 93% of
a 60-day transient. Declared breaks inflate the covariance at the first epoch
after them (break_mode: offset, rate or both), and the gate is suspended
there, so a real step is kept; the inflated covariance also stops the backward
pass at the break.
Unlike the study, each pixel keeps its own 2 x 2 covariance: DISP pixels have their own missing epochs, and the study's production notes ask for a per-pixel gate (unwrapping jumps) rather than its scene-wide one.
LOWESS (method="lowess"): robust local-linear regression (tricube
kernel of bandwidth_days, 60 d, and iterations bisquare reweightings,
2, per pixel), defined at any time. Its outlier rejection depends on sampling
density (0.1% of a spike survives at 12-day sampling, about 30% at 24-day).
Breaks are handled by fitting each segment separately.
Known limitation: both methods were evaluated on one flat, aseismic site (Kiritimati). A deforming site with topography-correlated troposphere is part of the T29 trade-study frames.
denoise ¶
Temporal denoising: gated Kalman smoother and robust LOWESS (PRD S3, T31).
Both methods share one interface, :func:denoise, and evaluate on the
acquisitions or on a regular grid (:func:disp_xr.timeseries.regular_time_grid)
with refused gaps. They are for display, resampling, linearity checks and
transient hunting: the temporal-denoising study (Kiritimati DISP-S1, 2026-09)
found that no filter improves a velocity, because InSAR residuals at 12-day
sampling are white and ordinary least squares is already optimal. Fit rates on
the raw series; :func:disp_xr.fit_trajectory refuses data marked as denoised.
Kalman (method="kalman"): a local-linear-trend model (displacement and
velocity, an integrated random walk) with a Rauch-Tung-Striebel smoother, or a
fixed-lag smoother for products that publish as data arrive. The only knobs
are the timescale tau_days (20 to 30 d), which sets the process noise
q = 3 R / tau³ against the measurement variance R (estimated per pixel
from the data), and the χ² gate: an update whose normalized innovation squared
exceeds gate (9) is skipped. A one-epoch outlier makes one huge innovation
and is rejected; a real transient builds over several epochs that stay inside
the gate. In the study it left 0.2 to 0.4% of an injected spike and kept 93% of
a 60-day transient. Declared breaks inflate the covariance at the first epoch
after them (break_mode: offset, rate or both), and the gate is suspended
there, so a real step is kept; the inflated covariance also stops the backward
pass at the break.
Unlike the study, each pixel keeps its own 2 x 2 covariance: DISP pixels have their own missing epochs, and the study's production notes ask for a per-pixel gate (unwrapping jumps) rather than its scene-wide one.
LOWESS (method="lowess"): robust local-linear regression (tricube
kernel of bandwidth_days, 60 d, and iterations bisquare reweightings,
2, per pixel), defined at any time. Its outlier rejection depends on sampling
density (0.1% of a spike survives at 12-day sampling, about 30% at 24-day).
Breaks are handled by fitting each segment separately.
Known limitation: both methods were evaluated on one flat, aseismic site (Kiritimati). A deforming site with topography-correlated troposphere is part of the T29 trade-study frames.
q_from_timescale ¶
Return the process noise giving a smoothing timescale of tau_days.
For an integrated random walk the timescale where process noise balances
measurement noise r is tau = (3 r / q)^(1/3) (in years). Setting
q directly is how a filter silently returns a straight line (q too
small for r).
kalman_smooth ¶
kalman_smooth(t_years: ndarray, y: ndarray, *, tau_days: float = 20.0, gate: Optional[float] = 9.0, r: Optional[ndarray] = None, p0_velocity: float = 1.0, breaks: Optional[ndarray] = None, break_mode: Literal['offset', 'rate', 'both'] = 'offset', break_var: float = 100.0, lag: Optional[int] = None) -> tuple[np.ndarray, np.ndarray]
Gated local-linear-trend Kalman filter and smoother on (time, npix).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t_years
|
ndarray
|
Increasing times in years; epochs where |
required |
y
|
ndarray
|
|
required |
tau_days
|
float
|
Smoothing timescale and χ² gate (None turns the gate off). |
20.0
|
gate
|
float
|
Smoothing timescale and χ² gate (None turns the gate off). |
20.0
|
r
|
ndarray
|
Measurement variance per pixel; default estimated from |
None
|
p0_velocity
|
float
|
Prior velocity variance; must stay diffuse (a tight prior shrinks every rate toward zero), in data units squared per year squared. |
1.0
|
breaks
|
ndarray
|
Boolean per epoch: the covariance is inflated there. |
None
|
break_mode
|
('offset', 'rate', 'both')
|
What may jump at a break. |
"offset"
|
break_var
|
float
|
Variance added at a break, in data units squared (and per year squared for the rate). |
100.0
|
lag
|
int
|
Fixed-lag smoothing: epoch |
None
|
Returns:
| Type | Description |
|---|---|
(ndarray, ndarray)
|
Smoothed displacement and velocity, |
lowess ¶
lowess(t_years: ndarray, y: ndarray, t_out: ndarray, *, bandwidth_days: float = 60.0, iterations: int = 2, min_points: int = 3) -> np.ndarray
Robust local-linear regression of (time, npix) series at t_out.
The window at each output time is bandwidth_days wide on each side,
widened to the min_points nearest acquisitions where sampling is
sparse; pixels with fewer than min_points valid values in it are NaN.
Each of iterations passes reweights points by the bisquare of their
residual over six times the pixel's median absolute residual (Cleveland
1979, https://doi.org/10.1080/01621459.1979.10481038).
denoise ¶
denoise(obj: Union[Dataset, DataArray], method: Literal['kalman', 'lowess'] = 'kalman', *, grid: Union[None, str, Sequence, DatetimeIndex] = None, layer: str = 'displacement', tau_days: float = 20.0, gate: Optional[float] = 9.0, lag: Optional[int] = None, bandwidth_days: float = 60.0, iterations: int = 2, breaks: Optional[Sequence[Any]] = None, break_mode: Literal['offset', 'rate', 'both'] = 'offset', max_gap_days: Optional[float] = None) -> Any
Denoise displacement series in time, on the acquisitions or a grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Dataset or DataArray
|
|
required |
method
|
('kalman', 'lowess')
|
See the module docstring. |
"kalman"
|
grid
|
(None, 'auto' or dates)
|
Output times: the acquisitions (None), a regular grid
( |
None
|
layer
|
str
|
Layer of a Dataset. |
'displacement'
|
tau_days
|
float
|
Kalman timescale, χ² gate (None: off) and fixed lag (None: RTS). |
20.0
|
gate
|
float
|
Kalman timescale, χ² gate (None: off) and fixed lag (None: RTS). |
20.0
|
lag
|
float
|
Kalman timescale, χ² gate (None: off) and fixed lag (None: RTS). |
20.0
|
bandwidth_days
|
float
|
LOWESS half-window and robust passes. |
60.0
|
iterations
|
float
|
LOWESS half-window and robust passes. |
60.0
|
breaks
|
sequence of date-like
|
Declared discontinuities. |
None
|
break_mode
|
('offset', 'rate', 'both')
|
Kalman: what may jump at a break. |
"offset"
|
max_gap_days
|
float
|
Longest hole a grid may bridge (default from
:func: |
None
|
Returns:
| Type | Description |
|---|---|
Same type as ``obj``
|
Denoised |
Examples:
kalman_smooth ¶
kalman_smooth(t_years: ndarray, y: ndarray, *, tau_days: float = 20.0, gate: Optional[float] = 9.0, r: Optional[ndarray] = None, p0_velocity: float = 1.0, breaks: Optional[ndarray] = None, break_mode: Literal['offset', 'rate', 'both'] = 'offset', break_var: float = 100.0, lag: Optional[int] = None) -> tuple[np.ndarray, np.ndarray]
Gated local-linear-trend Kalman filter and smoother on (time, npix).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t_years
|
ndarray
|
Increasing times in years; epochs where |
required |
y
|
ndarray
|
|
required |
tau_days
|
float
|
Smoothing timescale and χ² gate (None turns the gate off). |
20.0
|
gate
|
float
|
Smoothing timescale and χ² gate (None turns the gate off). |
20.0
|
r
|
ndarray
|
Measurement variance per pixel; default estimated from |
None
|
p0_velocity
|
float
|
Prior velocity variance; must stay diffuse (a tight prior shrinks every rate toward zero), in data units squared per year squared. |
1.0
|
breaks
|
ndarray
|
Boolean per epoch: the covariance is inflated there. |
None
|
break_mode
|
('offset', 'rate', 'both')
|
What may jump at a break. |
"offset"
|
break_var
|
float
|
Variance added at a break, in data units squared (and per year squared for the rate). |
100.0
|
lag
|
int
|
Fixed-lag smoothing: epoch |
None
|
Returns:
| Type | Description |
|---|---|
(ndarray, ndarray)
|
Smoothed displacement and velocity, |
lowess ¶
lowess(t_years: ndarray, y: ndarray, t_out: ndarray, *, bandwidth_days: float = 60.0, iterations: int = 2, min_points: int = 3) -> np.ndarray
Robust local-linear regression of (time, npix) series at t_out.
The window at each output time is bandwidth_days wide on each side,
widened to the min_points nearest acquisitions where sampling is
sparse; pixels with fewer than min_points valid values in it are NaN.
Each of iterations passes reweights points by the bisquare of their
residual over six times the pixel's median absolute residual (Cleveland
1979, https://doi.org/10.1080/01621459.1979.10481038).
q_from_timescale ¶
Return the process noise giving a smoothing timescale of tau_days.
For an integrated random walk the timescale where process noise balances
measurement noise r is tau = (3 r / q)^(1/3) (in years). Setting
q directly is how a filter silently returns a straight line (q too
small for r).
Colored noise (one series)¶
Colored-noise trend estimation for one time series (maximum likelihood).
The deterministic model (a :class:~disp_xr.timeseries.trajectory.Trajectory
or any design matrix) is fitted by generalized least squares inside the
likelihood of a noise model, and the noise parameters are estimated by
(restricted) maximum likelihood. The resulting sigmas account for temporal
correlation, which ordinary least squares ignores: for GNSS and InSAR series
OLS velocity sigmas are often 5 to 10 times too small.
Noise models:
"WN"
White noise only (equivalent to OLS).
"FNWN"
Flicker (spectral index -1) plus white.
"PLWN"
Power law with a free spectral index kappa plus white.
"RWFNWN"
Random walk (-2), flicker and white.
Each likelihood evaluation factorizes an n x n covariance (O(n³)), so this
is for one series or a group of pixels; full frames use the vectorized
estimators of plan task T22. method="whittle" estimates the noise from the
periodogram of the OLS residuals (O(n log n)) and makes one exact GLS solve.
The method follows Hector / HectorP (Bos et al.), written from the papers:
- Bos, M. S., Fernandes, R. M. S., Williams, S. D. P., & Bastos, L. (2013). Fast error analysis of continuous GNSS observations with missing data. Journal of Geodesy, 87(4), 351-360. https://doi.org/10.1007/s00190-012-0605-0
- Williams, S. D. P. (2003). The effect of coloured noise on the uncertainties of rates estimated from geodetic time series. Journal of Geodesy, 76(9-10), 483-494. https://doi.org/10.1007/s00190-002-0283-4
- Hosking, J. R. M. (1981). Fractional differencing. Biometrika, 68(1), 165-176. https://doi.org/10.1093/biomet/68.1.165
fit_noise ¶
fit_noise(times: Sequence, y: Sequence[float], design: Optional[ndarray] = None, *, terms: Optional[Trajectory] = None, names: Optional[Sequence[str]] = None, model: NoiseModel = 'PLWN', rmle: bool = True, method: Literal['exact', 'whittle'] = 'exact', sampling_days: Optional[float] = None) -> NoiseResult
Fit a trajectory to one series under a colored-noise model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times
|
sequence of datetime-like
|
Epochs; need not be regular. NaN values in |
required |
y
|
sequence of float
|
The series (any unit; outputs use the same unit). |
required |
design
|
ndarray
|
|
None
|
terms
|
Trajectory
|
Model terms when |
None
|
names
|
sequence of str
|
Coefficient names for a given |
None
|
model
|
('PLWN', 'FNWN', 'RWFNWN', 'WN')
|
Noise model; see the module docstring. |
"PLWN"
|
rmle
|
bool
|
Restricted maximum likelihood, which corrects the noise estimate for the degrees of freedom used by the deterministic model. |
True
|
method
|
('exact', 'whittle')
|
Time-domain (R)MLE (O(n³) per evaluation), or the noise from the
Whittle likelihood of the OLS residuals (O(n log n)) followed by one
exact GLS solve. Whittle supports |
"exact"
|
sampling_days
|
float
|
Nominal sampling interval of the regular grid the noise lives on. Default: the smallest spacing between epochs, in whole days. |
None
|
Returns:
| Type | Description |
|---|---|
NoiseResult
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than 10 epochs are valid, the model is rank deficient, or two epochs fall in one sampling interval. |
Examples:
NoiseResult
dataclass
¶
Downsampling¶
downsample ¶
Quality-weighted downsampling to a regular coarse grid (PRD S2, plan task T27).
Each output cell aggregates a factor x factor block of 30 m pixels:
- float layers: the weighted mean (
stat="wmean") or weighted median (stat="wmedian"), with the weighted spread, the effective sample countn_eff = (Σw)² / Σw²andsigma = spread / sqrt(n_eff); - integer mask and label layers: the most common value (
"mode") or whether any pixel is set ("any"), never a mean.
With spread, each float layer also gets its spatial variation within
the cell, all weighted with the same weights: <layer>_std (weighted
standard deviation of the pixels), <layer>_rmse (weighted RMS of the
pixels about the cell value; equal to the std for "wmean") and
<layer>_mad (weighted median of the absolute deviations from the
weighted median; times 1.4826 it estimates the std of Gaussian data).
Every cell also gets n_valid, valid_fraction (of the full block, so
edge cells padded beyond the frame show how much they hold) and quality
(mean static weight). Weighted-mean levels keep sum_w and sum_w2 so a
coarser mean can be rebuilt from them; a weighted median cannot, so every
median level is computed from 30 m. The sigma treats pixels as
independent, which is far too small at 1 to 10 km; pass a variogram model
(:mod:disp_xr.residuals) to also get <layer>_sigma_corr, which accounts
for spatially correlated errors.
Odd factors (33, 67, 333 for 990 m, 2,010 m, 9,990 m) put each cell centre on a 30 m pixel centre, so the coarse transform is exact. Run after chaining and re-referencing: averaging series with different reference dates is wrong.
downsample ¶
downsample(ds: Dataset, *, factor: Optional[int] = None, spacing: Optional[float] = None, stat: Stat = 'wmean', weights: Union[str, DataArray, None] = 'quality', layers: Optional[list[str]] = None, categorical: Optional[Mapping[str, Categorical]] = None, nan_threshold: float = 1.0, variogram: Optional[Mapping[str, VariogramModel]] = None, spread: Union[Spread, Sequence[Spread], None] = None) -> xr.Dataset
Aggregate a DISP dataset to coarse cells with quality weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Chained, re-referenced data with |
required |
factor
|
int
|
Pixels per cell side. Odd factors keep cell centres on pixel centres (33, 67, 333 for about 1, 2 and 10 km at 30 m). |
None
|
spacing
|
float
|
Cell size in the CRS unit instead of |
None
|
stat
|
('wmean', 'wmedian')
|
Weighted mean or weighted median of float layers. |
"wmean"
|
weights
|
('quality', DataArray or None)
|
|
'quality'
|
layers
|
list of str
|
Float layers to aggregate; default every float layer with |
None
|
categorical
|
mapping
|
Mask and label layers and their reduction, |
None
|
nan_threshold
|
float
|
Largest share of missing pixels (of the full block) a cell may have;
cells above it become NaN. 1.0 keeps any cell with data. Follows
xarray-regrid's |
1.0
|
variogram
|
mapping of str to VariogramModel
|
A fitted variogram (:func: |
None
|
spread
|
('std', 'rmse', 'mad')
|
Spatial variation of each float layer within the cell, weighted
with the same weights: |
"std"
|
Returns:
| Type | Description |
|---|---|
Dataset
|
One variable per aggregated layer on the coarse grid, with
|
Examples:
to_points ¶
Return the occupied cells of a downsampled dataset as points.
The result has dimension point (and time if present), ordered by
cell row then column, with the weighted centroid (centroid_x,
centroid_y), the cell centre (x, y), the cell id
(row * n_cols + col) and every cell variable. .to_dataframe()
gives the table.
Examples:
to_points ¶
Return the occupied cells of a downsampled dataset as points.
The result has dimension point (and time if present), ordered by
cell row then column, with the weighted centroid (centroid_x,
centroid_y), the cell centre (x, y), the cell id
(row * n_cols + col) and every cell variable. .to_dataframe()
gives the table.
Examples:
downsample_hex ¶
downsample_hex(ds: Dataset, *, spacing: Optional[float] = None, resolution: Optional[int] = None, grid: Literal['planar', 'h3'] = 'planar', stat: Stat = 'wmean', weights: Union[str, DataArray, None] = 'quality', layers: Optional[list[str]] = None, categorical: Optional[Mapping[str, Categorical]] = None, nan_threshold: float = 1.0, variogram: Optional[Mapping[str, VariogramModel]] = None, spread: Union[Spread, Sequence[Spread], None] = None) -> xr.Dataset
Aggregate a DISP dataset to hexagonal cells with quality weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Chained, re-referenced data with |
required |
spacing
|
float
|
|
None
|
resolution
|
int
|
|
None
|
grid
|
('planar', 'h3')
|
Planar hexagons in the frame's CRS anchored at its origin, or H3
global cells (the |
"planar"
|
stat
|
Stat
|
As in :func: |
'wmean'
|
weights
|
Stat
|
As in :func: |
'wmean'
|
layers
|
Stat
|
As in :func: |
'wmean'
|
categorical
|
Stat
|
As in :func: |
'wmean'
|
nan_threshold
|
Stat
|
As in :func: |
'wmean'
|
variogram
|
Stat
|
As in :func: |
'wmean'
|
spread
|
Stat
|
As in :func: |
'wmean'
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Dimension |
Examples:
quadtree ¶
Quality-weighted quadtree downsampling (PRD S2 quadtree mode, plan task T28).
Cells of level k cover 2**k x 2**k pixels of the 30 m grid, anchored
at its first pixel, from min_level (finest) to max_level (coarsest).
A cell splits into its four children while its split score exceeds a
threshold and every child keeps enough reliable pixels; the cells that do
not split are the leaves. Smooth areas end up in large cells and areas with
structure (deformation, unwrapping errors) in small ones.
The per-cell sums of :func:disp_xr.downsample (Σw, Σw², Σwv, Σwv², valid
count) compose exactly: a level is the 2 x 2 block sum of the level below.
So the data are read twice: once for the sums of the finest level (and from
them every coarser level and every score), once for the values of the
leaves. Leaf selection works on the small score maps, level by level, as
boolean masks, with no recursion per cell.
Split metrics (metric):
"displacement": RMS over epochs of the weighted spread of the pixels in the cell, in the unit of the data, so a threshold reads as "split where pixels in a cell differ by more than 5 mm";"velocity": weighted spread of a per-pixel velocity (passvelocity, for example from :func:disp_xr.fit_velocity), in its unit;"structure": the spread of the four children's means divided by their noise (the mean of their sigmas), both RMS over epochs: signal structure at the cell scale against noise, after the structure function of GPS Imaging (Hammond et al. 2016, https://doi.org/10.1002/2016JB013458). Unitless; split above about 2;- or a callable
metric(level, children) -> (y, x) DataArraythat gets the level's sums as a Dataset and those of its children (None atmin_level).
Plan task T29 compares the three and sets defaults.
Quadtree ¶
Bases: NamedTuple
Leaves of a quadtree.
Examples:
>>> tree = dxr.quadtree(ds, n_points=5000)
>>> tree.points.level.to_series().value_counts()
>>> tree.to_raster("displacement", time=-1).plot.imshow()
leaf_id
instance-attribute
¶
(y, x) index into points of the leaf holding each pixel, -1
for pixels in no leaf (no reliable pixels in the cell).
to_raster ¶
Return a leaf variable painted back on the (y, x) grid.
time is an epoch index (default the last) or a date (nearest);
pixels in no leaf are NaN.
quadtree ¶
quadtree(ds: Dataset, *, threshold: Optional[float] = None, n_points: Optional[int] = None, metric: Metric = 'structure', min_level: int = 3, max_level: int = 8, min_reliable: float = 0.5, layer: str = 'displacement', weights: Union[str, DataArray, None] = 'quality', velocity: Optional[DataArray] = None) -> Quadtree
Downsample to quadtree leaves: small cells where the data vary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
Dataset
|
Chained, re-referenced data with |
required |
threshold
|
float
|
Split score above which a cell splits (see the module docstring for the unit of each metric). |
None
|
n_points
|
int
|
Number of leaves wanted instead of a threshold: the threshold whose
leaf count is closest is chosen (recorded as |
None
|
metric
|
('displacement', 'velocity', 'structure')
|
Split metric. Default |
"displacement"
|
min_level
|
int
|
Finest and coarsest levels: cells of |
3
|
max_level
|
int
|
Finest and coarsest levels: cells of |
3
|
min_reliable
|
float
|
Share of a child's pixels inside the frame that must be reliable (static weight above 0 and data at some epoch) in each of the four children for a cell to split. Children outside the frame are ignored, so cells on the frame edge split like the others. |
0.5
|
layer
|
str
|
Layer to aggregate and score. |
'displacement'
|
weights
|
('quality', DataArray or None)
|
As in :func: |
'quality'
|
velocity
|
DataArray
|
|
None
|
Returns:
| Type | Description |
|---|---|
Quadtree
|
|
Examples:
Quadtree ¶
Bases: NamedTuple
Leaves of a quadtree.
Examples:
>>> tree = dxr.quadtree(ds, n_points=5000)
>>> tree.points.level.to_series().value_counts()
>>> tree.to_raster("displacement", time=-1).plot.imshow()
leaf_id
instance-attribute
¶
(y, x) index into points of the leaf holding each pixel, -1
for pixels in no leaf (no reliable pixels in the cell).
to_raster ¶
Return a leaf variable painted back on the (y, x) grid.
time is an epoch index (default the last) or a date (nearest);
pixels in no leaf are NaN.
Quality-weighted downsampling to hexagonal cells (PRD S2 hexagon mode, T27.8).
Pixels are grouped into pointy-top hexagons of a planar lattice in the
frame's CRS, anchored at the CRS origin, so overlapping frames of one UTM
zone share cell ids. spacing is the distance between neighboring cell
centers (the flat-to-flat width); a hexagon covers sqrt(3) / 2 * spacing²,
so spacing = 1.0746 * side matches the area of a square of that side.
Each pixel goes to the hexagon whose center is nearest, found with axial
coordinates and cube rounding (Patel, "Hexagonal Grids", Red Blob Games).
The per-cell statistics are those of :func:disp_xr.downsample (weighted
mean or median, sigma, n_eff, valid fraction, quality, weighted
centroid, mode or "any" for masks and labels), computed with grouped sums
(one np.bincount per dask block) instead of block sums. Hexagons are not
a raster, so the result is a (time, point) table in the
:func:disp_xr.to_points layout.
Why hexagons: all six neighbors are equidistant and a hexagon is more
compact than a square of equal area, so the sampling is more isotropic
(Birch et al. 2007, https://doi.org/10.1016/j.ecolmodel.2007.03.041). The
lattice is the one matplotlib.hexbin and PostGIS ST_HexagonGrid
build (Carr et al. 1987, https://doi.org/10.1080/01621459.1987.10478445);
here the cells carry quality weights and sigmas, lazily.
hex_axial ¶
Return the axial coordinates (q, r) of the hexagon holding each point.
The lattice is pointy-top, anchored at (0, 0), with centers at
x = spacing * (q + r / 2), y = spacing * sqrt(3) / 2 * r.
hex_centers ¶
Return the center (x, y) of hexagons (q, r).
hexagon_vertices ¶
Return the six vertices of pointy-top hexagons centered at (x, y).
Shape (n, 6, 2), for matplotlib.collections.PolyCollection or
shapely polygons.
cell_polygons ¶
Return the outline of every cell of downsample_hex in the data CRS.
One (k, 2) array of (x, y) vertices per point.
to_geodataframe ¶
Return the cells of downsample_hex as a GeoDataFrame of polygons.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hexes
|
Dataset
|
Output of :func: |
required |
time
|
(int, date - like or None)
|
Epoch to take for time-dependent variables: an index (default the last), a date (nearest), or None for one row per cell and epoch. |
-1
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
One row per cell (and epoch), the cell variables, the coordinates and
the hexagon polygon, in the data CRS (needs the |
Examples:
Correlated-error sigma¶
empirical_variogram ¶
empirical_variogram(field: DataArray, *, n_pairs: int = 200000, n_bins: int = 30, max_distance: Optional[float] = None, seed: int = 0) -> xr.Dataset
Estimate the empirical semivariogram of a 2D field from random pairs.
Each pair starts at a random valid pixel; its partner lies at a distance
drawn log-uniformly between one pixel and max_distance, in a random
direction. Every logarithmic distance bin then gets about the same number
of pairs, unlike all pairs of a random sample, which leave short lags
(where the cell sigma is decided) nearly empty.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
field
|
DataArray
|
A |
required |
n_pairs
|
int
|
Number of pairs drawn (pairs whose partner is NaN or off the grid are dropped). |
200000
|
n_bins
|
int
|
Number of logarithmic distance bins. |
30
|
max_distance
|
float
|
Largest distance, in the CRS unit; default half the shorter side of the field. |
None
|
seed
|
int
|
Random seed. |
0
|
Returns:
| Type | Description |
|---|---|
Dataset
|
Dimension |
Examples:
fit_variogram ¶
fit_variogram(empirical: Dataset, models: Sequence[ModelKind] = ('spherical', 'spherical'), *, nugget: bool = True) -> VariogramModel
Fit a nugget and a sum of models to an empirical variogram.
Weighted least squares with the approximate standard error of each bin,
gamma * sqrt(2 / count), so every bin counts by its relative misfit
and short lags are not swamped by the large semivariances of long ones.
Two spherical models (a short and a long range) are xDEM's default for
elevation residuals and fit InSAR residuals (turbulent troposphere plus
long-wavelength errors) well.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
empirical
|
Dataset
|
Output of :func: |
required |
models
|
sequence of {"spherical", "exponential", "gaussian"}
|
One entry per component, from short to long range. |
('spherical', 'spherical')
|
nugget
|
bool
|
Fit a nugget (uncorrelated pixel noise); otherwise it is 0. |
True
|
Returns:
| Type | Description |
|---|---|
VariogramModel
|
|
Examples:
VariogramModel
dataclass
¶
A nugget plus a sum of bounded variogram models.
gamma(h) = nugget + sum(sill_k * f_k(h / range_k)) for h > 0 and
0 at h = 0. Ranges are practical ranges (the semivariance reaches
95% of the partial sill for exponential and Gaussian models, all of it
for spherical ones), in the CRS unit of the field.
Examples:
Residuals: spatial variograms (T27.3b) and DISP against GNSS (T32).
:func:compare compares DISP with GNSS at the stations, with geepers
computing the metrics (see its docstring). The rest of this docstring
covers the variograms.
The sigma of a cell mean computed as spread / sqrt(n) assumes independent
pixels. InSAR errors (troposphere, unwrapping, orbits) are correlated over
kilometres, so at 1 to 10 km cells that sigma is far too small. Following
xDEM (Hugonnet et al. 2022), this module
- estimates an empirical variogram of a residual field (a field that should
be zero: stable ground, or data minus a model) from a random sample of
pixel pairs, with distances drawn log-uniformly so short lags are as well
sampled as long ones (:func:
empirical_variogram); - fits a sum of spherical, exponential or Gaussian models and a nugget
(:func:
fit_variogram); - turns the model into the effective number of independent samples of a
block mean (:func:
mean_block_correlation, :func:n_eff_correlated), which :func:disp_xr.downsampleuses for<layer>_sigma_corr.
The residual semivariogram of plan task T32.1 reuses this code.
References
Hugonnet, R., et al. (2022). Uncertainty analysis of digital elevation models by spatial inference from stable terrain. IEEE JSTARS 15, 6456-6472. https://doi.org/10.1109/JSTARS.2022.3188922
Rolstad, C., Haug, T. and Denby, B. (2009). Spatially integrated geodetic glacier mass balance and its uncertainty based on geostatistical analysis. Journal of Glaciology 55(192), 666-680. https://doi.org/10.3189/002214309789470950
mean_block_correlation ¶
mean_block_correlation(model: VariogramModel, factor: int, spacing: Union[float, tuple[float, float]]) -> float
Return the mean correlation between two distinct pixels of a block.
Exact for a full factor x factor block: the double sum over pixel
pairs is a sum over the (2 factor - 1)² pixel offsets, each counted
(factor - |dy|) (factor - |dx|) times.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
VariogramModel
|
The fitted variogram. |
required |
factor
|
int
|
Pixels per block side. |
required |
spacing
|
float or (float, float)
|
Pixel spacing, or |
required |
Examples:
mean_mask_correlation ¶
mean_mask_correlation(model: VariogramModel, mask: ndarray, spacing: Union[float, tuple[float, float]]) -> float
Return the mean correlation between two distinct pixels of a cell shape.
mask marks the pixels of one full cell (a hexagon, for example). The
number of pixel pairs at each offset is the autocorrelation of the mask,
computed with an FFT, so the double sum over pairs costs one transform.
:func:mean_block_correlation is the square-block case.
Examples:
n_eff_correlated ¶
Return the effective sample count of a weighted mean of correlated pixels.
The variance of a weighted mean is sill * Σᵢⱼ wᵢ wⱼ ρᵢⱼ / (Σw)².
Replacing each off-diagonal ρᵢⱼ by the block mean rho_bar gives
n_eff = (Σw)² / (Σw² + ((Σw)² - Σw²) rho_bar): (Σw)² / Σw² for
independent pixels, 1 for fully correlated ones. Exact for a full block
with equal weights; it assumes valid pixels and weights are spread
evenly over the block, so it overestimates n_eff (underestimates the
sigma) for blocks whose valid pixels cluster, such as edge cells.
Examples:
Units, geometry and checks¶
to_units ¶
Convert every length variable of obj to unit (m, cm or mm).
Variables whose units attribute is not a length (unitless,
radians) are returned unchanged.
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
enu_to_los ¶
enu_to_los(east: ArrayT, north: ArrayT, up: ArrayT, los: Dataset | tuple[ArrayT, ArrayT, ArrayT]) -> ArrayT
Project east, north and up motion onto the line of sight.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
east
|
(float, array or DataArray)
|
Motion components, in any unit; the result has the same unit. |
required |
north
|
(float, array or DataArray)
|
Motion components, in any unit; the result has the same unit. |
required |
up
|
(float, array or DataArray)
|
Motion components, in any unit; the result has the same unit. |
required |
los
|
Dataset or tuple
|
A dataset with |
required |
Returns:
| Type | Description |
|---|---|
Same type as the inputs
|
LOS motion, positive toward the satellite. |
Examples:
validate_disp ¶
validate_disp(obj: Union[Dataset, DataArray], *, variables: Sequence[str] = (), require_reference_time: bool = False, per_pixel: bool = False) -> None
Raise if obj is not a usable DISP stack; list every problem found.
Checks dims (time, y, x), monotonic x and y coordinates, a CRS,
the requested variables and, optionally, the reference_time
coordinate. With per_pixel=True, warns when time is split across
dask chunks, since per-pixel operations then rechunk (slow on big frames).
Raises:
| Type | Description |
|---|---|
DispValidationError
|
If any check fails. |
Examples: