Skip to content

Here is the API reference for the sarxarray package.

SLC stack module

sarxarray.stack.Stack

Stack(xarray_obj)

Methods:

  • mrm

    Compute a Mean Reflection Map (MRM).

  • multi_look

    Perform multi-looking on a Stack, and return a Stack.

  • point_selection

    Select pixels from a Stack, and return a Space-Time Matrix.

Source code in sarxarray/stack.py
def __init__(self, xarray_obj):
    self._obj = xarray_obj

mrm

mrm()

Compute a Mean Reflection Map (MRM).

Source code in sarxarray/stack.py
def mrm(self):
    """Compute a Mean Reflection Map (MRM)."""
    t_order = list(self._obj.sizes).index("time")  # Time dimension index
    return self._obj.amplitude.mean(axis=t_order)

multi_look

multi_look(window_size, method='coarsen', statistics='mean', compute=True)

Perform multi-looking on a Stack, and return a Stack.

Parameters:

  • data (Dataset) –

    The data to be multi-looked.

  • window_size (tuple) –

    Window size for multi-looking, in the format of (azimuth, range)

  • method (str, default: 'coarsen' ) –

    Method of multi-looking, by default "coarsen"

  • statistics (str, default: 'mean' ) –

    Statistics method for multi-looking, by default "mean"

  • compute (bool, default: True ) –

    Whether to compute the result, by default True. If False, the result will be dask.delayed.Delayed. This is useful when the multi_look is used as an intermediate result.

Returns:

  • Dataset

    An xarray.Dataset with coarsen shape if compute is True, otherwise a dask.delayed.Delayed object.

Source code in sarxarray/stack.py
def multi_look(
    self, window_size, method="coarsen", statistics="mean", compute=True
):
    """Perform multi-looking on a Stack, and return a Stack.

    Parameters
    ----------
    data : xarray.Dataset
        The data to be multi-looked.
    window_size : tuple
        Window size for multi-looking, in the format of (azimuth, range)
    method : str, optional
        Method of multi-looking, by default "coarsen"
    statistics : str, optional
        Statistics method for multi-looking, by default "mean"
    compute : bool, optional
        Whether to compute the result, by default True. If False, the result
        will be `dask.delayed.Delayed`. This is useful when the multi_look
        is used as an intermediate result.

    Returns
    -------
    xarray.Dataset
        An `xarray.Dataset` with coarsen shape if
        `compute` is True, otherwise a `dask.delayed.Delayed` object.
    """
    return multi_look(self._obj, window_size, method, statistics, compute)

point_selection

point_selection(threshold, method='amplitude_dispersion', chunks=1000)

Select pixels from a Stack, and return a Space-Time Matrix.

The selection method is defined by method and threshold. The selected pixels will be reshaped to (space, time), where space is the number of selected pixels. The unselected pixels will be discarded. The original azimuth and range coordinates will be persisted.

Parameters:

  • threshold (float) –

    Threshold value for selection

  • method (str, default: 'amplitude_dispersion' ) –

    Method of selection, by default "amplitude_dispersion"

  • chunks (int, default: 1000 ) –

    Chunk size in the space dimension, by default 1000

Returns:

  • Dataset

    An xarray.Dataset with two dimensions: (space, time).

Source code in sarxarray/stack.py
def point_selection(self, threshold, method="amplitude_dispersion", chunks=1000):
    """Select pixels from a Stack, and return a Space-Time Matrix.

    The selection method is defined by `method` and `threshold`.
    The selected pixels will be reshaped to (space, time), where `space` is
    the number of selected pixels. The unselected pixels will be discarded.
    The original `azimuth` and `range` coordinates will be persisted.

    Parameters
    ----------
    threshold : float
        Threshold value for selection
    method : str, optional
        Method of selection, by default "amplitude_dispersion"
    chunks : int, optional
        Chunk size in the space dimension, by default 1000

    Returns
    -------
    xarray.Dataset
        An xarray.Dataset with two dimensions: (space, time).
    """
    match method:
        case "amplitude_dispersion":
            # Amplitude dispersion thresholding
            # Note there can be NaN values in the amplitude dispersion
            # However NaN values will not pass this threshold
            mask = self._amp_disp() < threshold
        case _:
            raise NotImplementedError

    # Get the 1D index on space dimension
    mask_1d = mask.stack(space=("azimuth", "range")).drop_vars(
        ["azimuth", "range", "space"]
    )
    index = mask_1d.space.data[mask_1d.data]  # Evaluate the mask

    # Reshape from Stack ("azimuth", "range", "time") to Space-Time Matrix
    # ("space", "time")
    stacked = self._obj.stack(space=("azimuth", "range"))
    stm = stacked.drop_vars(
        ["space", "azimuth", "range"]
    )  # this will also drop azimuth and range
    stm = stm.assign_coords(
        {
            "azimuth": (["space"], stacked.azimuth.data),
            "range": (["space"], stacked.range.data),
        }
    )  # keep azimuth and range index

    # Apply selection
    stm_masked = stm.sel(space=index)

    # Re-order the dimensions to
    # community preferred ("space", "time") order
    # Since there are dask arrays in stm_masked,
    # this operation is lazy.
    # Therefore its effect can be observed after evaluation
    stm_masked = stm_masked.transpose("space", "time")

    # Rechunk is needed because after apply maksing,
    # the chunksize will be in consistant
    stm_masked = stm_masked.chunk(
        {
            "space": chunks,
            "time": -1,
        }
    )

    return stm_masked

I/O module

sarxarray._io.from_dataset

from_dataset(ds: Dataset) -> Dataset

Create a SLC stack or from an Xarray Dataset.

This function create tasks graph converting the two data variables of complex data: real and imag, to three variables: complex, amplitude, and phase.

The function is intented for an SLC stack in xr.Dataset loaded from a Zarr file.

For other datasets, such as lat, lon, etc., please use xr.open_zarr directly.

Parameters:

  • ds (Dataset) –

    SLC stack loaded from a Zarr file. Must have three dimensions: (azimuth, range, time). Must have two variables: real and imag.

Returns:

  • Dataset

    Converted SLC stack. An xarray.Dataset with three dimensions: (azimuth, range, time), and three variables: complex, amplitude, phase.

Raises:

  • ValueError

    The input dataset should have three dimensions: (azimuth, range, time).

  • ValueError

    The input dataset should have the following variables: ('real', 'imag').

Source code in sarxarray/_io.py
def from_dataset(ds: xr.Dataset) -> xr.Dataset:
    """Create a SLC stack or from an Xarray Dataset.

    This function create tasks graph converting the two data variables of complex data:
    `real` and `imag`, to three variables: `complex`, `amplitude`, and `phase`.

    The function is intented for an SLC stack in `xr.Dataset` loaded from a Zarr file.

    For other datasets, such as lat, lon, etc., please use `xr.open_zarr` directly.

    Parameters
    ----------
    ds : xr.Dataset
        SLC stack loaded from a Zarr file.
        Must have three dimensions: `(azimuth, range, time)`.
        Must have two variables: `real` and `imag`.

    Returns
    -------
    xr.Dataset
        Converted SLC stack.
        An xarray.Dataset with three dimensions: `(azimuth, range, time)`, and
        three variables: `complex`, `amplitude`, `phase`.

    Raises
    ------
    ValueError
        The input dataset should have three dimensions: `(azimuth, range, time)`.
    ValueError
        The input dataset should have the following variables: `('real', 'imag')`.
    """
    # Check ds should have the following dimensions: (azimuth, range, time)
    if any(dim not in ds.sizes for dim in ["azimuth", "range", "time"]):
        raise ValueError(
            "The input dataset should have three dimensions: (azimuth, range, time)."
        )

    # Check ds should have the following variables: ("real", "imag")
    if any(var not in ds.variables for var in ["real", "imag"]):
        raise ValueError(
            "The input dataset should have the following variables: ('real', 'imag')."
        )

    # Construct the three datavariables: complex, amplitude, and phase
    ds["complex"] = ds["real"] + 1j * ds["imag"]
    ds = ds.slcstack._get_amplitude()
    ds = ds.slcstack._get_phase()

    # Remove the original real and imag variables
    ds = ds.drop_vars(["real", "imag"])

    return ds

sarxarray._io.from_binary

from_binary(slc_files, shape, vlabel='complex', dtype=complex64, chunks=None, ratio=1)

Read a SLC stack or related variables from binary files.

Parameters:

  • slc_files (Iterable) –

    Paths to the SLC files.

  • shape (Tuple) –

    Shape of each SLC file, in (n_azimuth, n_range)

  • vlabel (str, default: 'complex' ) –

    Name of the variable to read, by default "complex".

  • dtype (dtype, default: complex64 ) –

    Data type of the file to read, by default np.float32

  • chunks (list, default: None ) –

    2-D chunk size, by default None

  • ratio

    Ratio of resolutions (azimuth/range), by default 1

Returns:

  • Dataset

    An xarray.Dataset with three dimensions: (azimuth, range, time).

Source code in sarxarray/_io.py
def from_binary(
    slc_files, shape, vlabel="complex", dtype=np.complex64, chunks=None, ratio=1
):
    """Read a SLC stack or related variables from binary files.

    Parameters
    ----------
    slc_files : Iterable
        Paths to the SLC files.
    shape : Tuple
        Shape of each SLC file, in (n_azimuth, n_range)
    vlabel : str, optional
        Name of the variable to read, by default "complex".
    dtype : numpy.dtype, optional
        Data type of the file to read, by default np.float32
    chunks : list, optional
        2-D chunk size, by default None
    ratio:
        Ratio of resolutions (azimuth/range), by default 1

    Returns
    -------
    xarray.Dataset
        An xarray.Dataset with three dimensions: (azimuth, range, time).

    """
    # Check dtype
    if not np.dtype(dtype).isbuiltin:
        if not all([name in (("re", "im")) for name in dtype.names]):
            raise TypeError(
                "The customed dtype should have only two field names: "
                '"re" and "im". For example: '
                'dtype = np.dtype([("re", np.float32), ("im", np.float32)]).'
            )

    # Initialize stack as a Dataset
    coords = {
        "azimuth": range(shape[0]),
        "range": range(shape[1]),
        "time": range(len(slc_files)),
    }
    ds_stack = xr.Dataset(coords=coords)

    # Calculate appropriate chunk size if not user-defined
    if chunks is None:
        chunks = _calc_chunksize(shape, dtype, ratio)

    # Read in all SLCs
    slcs = None
    for f_slc in slc_files:
        if slcs is None:
            slcs = _mmap_dask_array(f_slc, shape, dtype, chunks).reshape(
                (shape[0], shape[1], 1)
            )
        else:
            slc = _mmap_dask_array(f_slc, shape, dtype, chunks).reshape(
                (shape[0], shape[1], 1)
            )
            slcs = da.concatenate([slcs, slc], axis=2)

    # unpack the customized dtype
    if not np.dtype(dtype).isbuiltin:
        meta_arr = np.array((), dtype=_dtypes["complex"])
        slcs = da.apply_gufunc(_unpack_complex, "()->()", slcs, meta=meta_arr)

    ds_stack = ds_stack.assign({vlabel: (("azimuth", "range", "time"), slcs)})

    # If reading complex data, automatically
    if vlabel == "complex":
        ds_stack = ds_stack.slcstack._get_amplitude()
        ds_stack = ds_stack.slcstack._get_phase()

    return ds_stack

Utility

sarxarray.utils.multi_look

multi_look(data, window_size, method='coarsen', statistics='mean', compute=True)

Perform multi-looking on a Stack, and return a Stack.

Parameters:

  • data (Dataset or DataArray) –

    The data to be multi-looked.

  • window_size (tuple) –

    Window size for multi-looking, in the format of (azimuth, range)

  • method (str, default: 'coarsen' ) –

    Method of multi-looking, by default "coarsen"

  • statistics (str, default: 'mean' ) –

    Statistics method for multi-looking, by default "mean"

  • compute (bool, default: True ) –

    Whether to compute the result, by default True. If False, the result will be dask.delayed.Delayed. This is useful when the multi_look is used as an intermediate result.

Returns:

  • Dataset or DataArray

    An xarray.Dataset or xarray.DataArray with coarsen shape if compute is True, otherwise a dask.delayed.Delayed object.

Source code in sarxarray/utils.py
def multi_look(data, window_size, method="coarsen", statistics="mean", compute=True):
    """Perform multi-looking on a Stack, and return a Stack.

    Parameters
    ----------
    data : xarray.Dataset or xarray.DataArray
        The data to be multi-looked.
    window_size : tuple
        Window size for multi-looking, in the format of (azimuth, range)
    method : str, optional
        Method of multi-looking, by default "coarsen"
    statistics : str, optional
        Statistics method for multi-looking, by default "mean"
    compute : bool, optional
        Whether to compute the result, by default True. If False, the result
        will be `dask.delayed.Delayed`. This is useful when the multi_look
        is used as an intermediate result.

    Returns
    -------
    xarray.Dataset or xarray.DataArray
        An `xarray.Dataset` or `xarray.DataArray` with coarsen shape if
        `compute` is True, otherwise a `dask.delayed.Delayed` object.
    """
    # validate the input
    _validate_multi_look_inputs(data, window_size, method, statistics)

    # chunk data if not already chunked
    if isinstance(data, Delayed) or not data.chunks:
        data = data.chunk("auto")

    # get the chunk size
    if isinstance(data, Delayed):
        chunks = "auto"
    else:
        chunks = _get_chunks(data, window_size)

    # add new atrrs here because Delayed objects are immutable
    data.attrs["multi-look"] = f"{method}-{statistics}"

    # define custom coordinate function to define new coordinates starting
    # from 0: the inputs `reshaped` and `axis` are output of
    # `coarsen_reshape` internal function and are passed to the `coord_func`
    def _custom_coord_func(reshaped, axis):
        if axis[0] == 1 or axis[0] == 2:
            return np.arange(0, reshaped.shape[0], 1, dtype=int)
        else:
            return reshaped.flatten()

    if method == "coarsen":
        # TODO: if boundary and size should be configurable
        multi_looked = data.coarsen(
            {"azimuth": window_size[0], "range": window_size[1]},
            boundary="trim",
            side="left",
            coord_func=_custom_coord_func,
        )

    # apply statistics
    stat_functions = {
        "mean": multi_looked.mean,
        "median": multi_looked.median,
    }

    stat_function = stat_functions[statistics]
    if compute:
        multi_looked = stat_function(keep_attrs=True)
    else:
        multi_looked = delayed(stat_function)(keep_attrs=True)

    # Rechunk is needed because shape of the data will be changed after
    # multi-looking
    multi_looked = multi_looked.chunk(chunks)

    return multi_looked

sarxarray.utils.complex_coherence

complex_coherence(reference: DataArray, other: DataArray, window_size, compute=True)

Calculate complex coherence of two images.

Assume two images reference (R) and other (O), the complex coherence is defined as: numerator = mean(R * O) in a window denominator = mean(R * R) * mean(O * O`) in a window coherence = abs( numerator / sqrt(denominator) ), See the equation in chapter 28 in doris documentation

Parameters:

  • reference (DataArray) –

    The reference image to calculate complex coherence with.

  • other (DataArray) –

    The other image to calculate complex coherence with.

  • window_size (tuple) –

    Window size for multi-looking, in the format of (azimuth, range)

  • compute (bool, default: True ) –

    Whether to compute the result, by default True. If False, the result will be dask.delayed.Delayed. This is useful when the complex_coherence is used as an intermediate result.

Returns:

  • DataArray

    An xarray.DataArray if compute is True, otherwise a dask.delayed.Delayed object.

Source code in sarxarray/utils.py
def complex_coherence(
    reference: xr.DataArray, other: xr.DataArray, window_size, compute=True
):
    """Calculate complex coherence of two images.

    Assume two images reference (R) and other (O), the complex coherence is
    defined as:
    numerator = mean(R * O`) in a window
    denominator = mean(R * R`) * mean(O * O`) in a window
    coherence = abs( numerator / sqrt(denominator) ),
    See the equation in chapter 28 in [doris
    documentation](http://doris.tudelft.nl/software/doris_v4.02.pdf)

    Parameters
    ----------
    reference : xarray.DataArray
        The reference image to calculate complex coherence with.
    other : xarray.DataArray
        The other image to calculate complex coherence with.
    window_size : tuple
        Window size for multi-looking, in the format of (azimuth, range)
    compute : bool, optional
        Whether to compute the result, by default True. If False, the result
        will be `dask.delayed.Delayed`. This is useful when the complex_coherence
        is used as an intermediate result.

    Returns
    -------
    xarray.DataArray
        An `xarray.DataArray` if `compute` is True,
        otherwise a `dask.delayed.Delayed` object.
    """
    # check if the two images have the same shape
    if (
        reference.azimuth.size != other.azimuth.size
        or reference.range.size != other.range.size
    ):
        raise ValueError("The two images have different shape.")

    # check if dtype is complex
    if reference.dtype != np.complex64 or other.dtype != np.complex64:
        raise ValueError("The dtype of the two images must be complex64.")

    # calculate the numerator of the equation
    da = reference * other.conj()
    numerator = multi_look(
        da, window_size, method="coarsen", statistics="mean", compute=compute
    )

    # calculate the denominator of the equation
    da = reference * reference.conj()
    reference_mean = multi_look(
        da, window_size, method="coarsen", statistics="mean", compute=compute
    )

    da = other * other.conj()
    other_mean = multi_look(
        da, window_size, method="coarsen", statistics="mean", compute=compute
    )

    denominator = reference_mean * other_mean

    # calculate the coherence
    def _compute_coherence(numerator, denominator):
        return np.abs(numerator / np.sqrt(denominator))

    if compute:
        coherence = _compute_coherence(numerator, denominator)
    else:
        coherence = delayed(_compute_coherence)(numerator, denominator)

    return coherence