toad.shifts.methods.edge

Edge detection method for shifts detection.

Contains the edge detection algorithm with associated helper functions.

Created: May 19, 2026 (Sjoerd)

Functions

compute_abruptness_1D(edges_1d, values_1d, ...)

Compute abruptness for 1D data.

construct_detection_ts(values_1d, lmin, ...)

Construct a detection time series (edge detection algorithm).

sobel_edge_detection(values_1d[, ...])

Perform Sobel edge detection for a one-dimensional time series.

thin_and_threshold_edges(gradient[, ...])

Apply non-maximum suppression and thresholding to a 1D gradient.

Classes

EDGE([lmin, lmax, lcutoff, alpha, ...])

Detect abrupt shifts in a time series using edge detection.

class toad.shifts.methods.edge.EDGE(lmin=None, lmax=None, lcutoff=None, alpha=0.4, smoothing_scale='auto', gradient_threshold='relative', gradient_threshold_multiplier=0.5, ignore_nan_warnings=False)

Bases: ShiftsMethod

Detect abrupt shifts in a time series using edge detection. This method is based on the edge detection algorithm by Canny (1986), adapted for abrupt shifts in climate data by Bathiany et al. (2020), and adapted by Terpstra et al. (2025). The implementation used here is the simplified 1D version of the original 3D algorithm.

The algorithm works by:
  1. Smoothing the input time series using a Gaussian filter to reduce noise.

  2. Computing the gradient using the Sobel operator.

  3. Applying non-maximum suppression to thin the edges (i.e. keep only local maxima in the gradient)

  4. Applying thresholding to identify significantly high gradients.

  5. Computing abruptness at candidate edges (difference in segment means normalised by pooled std)

Note: EDGE does not work with NaN values so it will return a detection time series of all zeros if the input time series contains NaN values. Note: The algorithm works only on data with enough variability to compute

abruptness. With minimal variability or noise, this method will not be able to reliably detect any abrupt shifts.

Note: All parameters related to datalength assume they have time step unit.

Note: Unlike ASDETECT, EDGE abruptness scores are not normalised to [-1, 1]. Significance

filtering is deferred to toad.TOAD.compute_clusters() via shift_threshold. For the common 4-sigma abruptness cut used in Bathiany et al. (2020), pass shift_threshold=4 when clustering EDGE shifts.

Parameters:
  • lmin (int | None) – Minimum segment length for the abruptness measure. If None, defaults to 10% of the length of the time series. Recommended to manually set based on timescale of interest and data resolution (e.g. for annual data with 10-year timescale of abrupt shifts, set lmin=15).

  • lmax (int | None) – Maximum segment length for the abruptness measure. The algorithm aims to use lmax, but if available data is less it will use between lmax and lmin. If None, defaults to 20% of the length of the time series. Recommended to manually set based on timescale of interest and data resolution (e.g. for annual data with 10-year timescale of abrupt shifts, set lmax=30).

  • lcutoff (int | None) – Length of data to exclude on both sides of the edge when calculating abruptness. If None, defaults to 2% of the length of the time series. Recommended to manually set based on timescale of interest and data resolution (e.g. for annual data with 10-year timescale of abrupt shift, set lcutoff=3).

  • alpha (float) – Parameter to control how much the difference in standard deviations between the two segments around the edge reduces the pooled standard deviation used to calculate abruptness. A higher value increases the abruptness of edges in timeseries where variability changes significantly after a shift. Defaults to 0.4.

  • smoothing_scale (int | str | None) – Standard deviation for Gaussian kernel used to smooth the time series before calculating the gradient. Accepts an integer (sigma, in time steps), None to disable smoothing, the string “auto” to let the method choose a default (10% of the length of the time series). Recommended to manually set based on timescale of interest and data resolution (e.g. for annual data with 10-year timescale of abrupt shift, set smoothing_scale=10).

  • gradient_threshold (float | str | None) – Threshold for gradient edge detection. Either a float (absolute threshold) or “relative” (uses gradient_threshold_multiplier × max(gradient)). Defaults to “relative”.

  • gradient_threshold_multiplier (float) – Multiplier for max(gradient) when using relative thresholding. Defaults to 0.5.

  • ignore_nan_warnings (bool) – (Optional) If True, timeseries containing NaN values will be ignored, i.e. a detection time series of all zeros will be returned. If False, an error will be raised.

fit_predict(values_1d, times_1d)

Compute the detection time series for each grid cell in the 3D data array.

Parameters:
  • values_1d (ndarray[tuple[Any, ...], dtype[float64]]) – 1D array of values

  • times_1d (ndarray[tuple[Any, ...], dtype[float64]]) – 1D array of times (accepted for API compatibility; abruptness uses time-step indices, as in the reference 1D EDGE implementation)

Returns:

A 1D array of the same length as values_1d. Non-zero values are signed abruptness scores at candidate edges (0 elsewhere). Apply shift_threshold in toad.TOAD.compute_clusters() to filter significant shifts; use shift_threshold=4 for the usual 4-sigma abruptness cut.

Return type:

ndarray[tuple[Any, …], dtype[float64]]

toad.shifts.methods.edge.compute_abruptness_1D(edges_1d, values_1d, lcutoff, lmax, lmin, alpha=0.4)

Compute abruptness for 1D data.

Parameters:
  • edges_1d (ndarray[tuple[Any, ...], dtype[float64]]) – 1D array indicating the presence of edges (non-zero values)

  • values_1d (ndarray[tuple[Any, ...], dtype[float64]]) – 1D array of values (e.g., temperature, pressure, etc.)

  • lcutoff (int) – Timesteps excluded on each side of the edge before fitting segments

  • lmax (int) – Maximum segment length used for abruptness

  • lmin (int) – Minimum segment length required for a reliable fit

  • alpha (float) – Pooled-std adjustment for differing segment variances

Returns:

Signed abruptness at edge locations, 0 elsewhere.

Return type:

ndarray[tuple[Any, …], dtype[float64]]

toad.shifts.methods.edge.construct_detection_ts(values_1d, lmin, lmax, lcutoff, alpha, smoothing_scale, gradient_threshold='relative', gradient_threshold_multiplier=0.5, ignore_nan_warnings=False)

Construct a detection time series (edge detection algorithm).

Parameters:
  • values_1d (ndarray[tuple[Any, ...], dtype[float64]]) – 1D array of values (e.g., temperature, pressure, etc.)

  • lmin (int) – Minimum segment length for abruptness

  • lmax (int) – Maximum segment length for abruptness

  • lcutoff (int) – Timesteps excluded on each side of the edge

  • alpha (float) – Pooled-std adjustment for differing segment variances

  • smoothing_scale (int | None) – Gaussian smoothing sigma in timesteps, or None to disable

  • gradient_threshold (float | str | None) – Absolute or "relative" gradient threshold

  • gradient_threshold_multiplier (float) – Multiplier for relative gradient thresholding

  • ignore_nan_warnings (bool) – Passed for API parity with ASDETECT (see note below)

Returns:

Abrupt shift score time series, shape (n,). Signed abruptness at candidate edges, 0 elsewhere. Filter with shift_threshold in toad.TOAD.compute_clusters().

Return type:

ndarray[tuple[Any, …], dtype[float64]]

Note

As for ASDETECT, any NaN in values_1d yields an all-zero detection series. compute_shifts skips grid cells that contain NaNs before calling fit_predict.

toad.shifts.methods.edge.sobel_edge_detection(values_1d, smoothing_scale=10)

Perform Sobel edge detection for a one-dimensional time series.

Parameters:
  • values_1d (ndarray[tuple[Any, ...], dtype[float64]]) – 1D array of values (e.g., temperature, pressure, etc.)

  • smoothing_scale (int | None) – Length of smoothing in Gaussian filter. If None (or 0), no smoothing is applied.

Returns:

Gradient of the (smoothed) values_1d.

Return type:

ndarray[tuple[Any, …], dtype[float64]]

toad.shifts.methods.edge.thin_and_threshold_edges(gradient, threshold='relative', threshold_multiplier=0.5)

Apply non-maximum suppression and thresholding to a 1D gradient.

Parameters:
  • gradient (ndarray[tuple[Any, ...], dtype[float64]]) – 1D array of gradients

  • threshold (float | str | None) – Value above which the gradient must lie. Either a float (absolute threshold) or "relative" (uses threshold_multiplier × max(|gradient|)).

  • threshold_multiplier (float) – Multiplier for max(|gradient|) when using relative thresholding.

Returns:

1D array of gradient magnitudes at thinned edge locations, 0 elsewhere.

Return type:

ndarray[tuple[Any, …], dtype[float64]]