Source code for xlandsat._composite

# Copyright (c) 2022 The xlandsat developers.
# Distributed under the terms of the MIT License.
# SPDX-License-Identifier: MIT
"""
Generate and manipulate composites.
"""
import numpy as np
import skimage.exposure
import xarray as xr


[docs] def composite(scene, bands=("red", "green", "blue"), rescale_to=None, dtype="uint8"): """ Create a composite using the given bands. The composite will be an RGBA array if NaNs are present in any band (with transparency of the NaN pixels set to full), or RGB is no NaNs are present. The RGB(A) array is encoded using the given dtype for easier plotting with matplotlib. Optionally rescale each band to the given range for improved contrast. Parameters ---------- scene : :class:`xarray.Dataset` A Landsat scene, as read with :func:`xlandsat.load_scene`. bands : list of str A list of variable names from the scene that will be used as the composite's red, green, and blue channels, respectively. rescale_to : None or list If not None, then should be a list/tuple with the minimum and maximum reflectance ranges to use for rescaling. The same values are used for each band. Bands are rescaled separately. Example: ``rescale_to=[0, 0.5]``. Default is None. dtype : str or numpy dtype The type of the output array. Will determine the range of values in the composite. Float types will result in [-1, 1] range outputs. Default is ``"uint8"`` meaning outputs in [0, 255] range and a smaller memory footprint. Use a float type for higher radiometric precision. Returns ------- composite : :class:`xarray.DataArray` The composite as a 3D ``DataArray`` of type uint8. The first 2 dimensions are the same as the scene with the ``"channel"`` added as third dimension. Metadata from the scene is copied to the composite. Notes ----- .. tip:: Use the :meth:`xarray.DataArray.plot.imshow` method to plot the composite using matplotlib. """ nrows, ncols = scene[bands[0]].shape if np.any((np.isnan(scene[b]) for b in bands)): ndim = 4 else: ndim = 3 composite = np.empty((nrows, ncols, ndim), dtype=dtype) for i, band in enumerate(bands): if rescale_to is None: in_range = (np.nanmin(scene[band].values), np.nanmax(scene[band].values)) else: in_range = tuple(rescale_to) composite[:, :, i] = skimage.exposure.rescale_intensity( scene[band].values, out_range=dtype, in_range=in_range, ) if ndim == 4: if np.dtype(dtype) == np.uint8: vmax = 255 else: vmax = 1 composite[:, :, 3] = np.where( np.any([np.isnan(scene[b]) for b in bands], axis=0), 0, vmax, ) long_name = ( ", ".join(f"{scene[b].attrs['long_name']}" for b in bands) + " composite" ) name = f"composite_{'_'.join(bands)}" coordinates = {"channel": ["red", "green", "blue", "alpha"][:ndim]} coordinates.update(scene.coords) attrs = {"long_name": long_name} attrs.update(scene.attrs) composite = xr.DataArray( data=composite, dims=(scene[bands[0]].dims[0], scene[bands[0]].dims[1], "channel"), coords=coordinates, attrs=attrs, name=name, ) return composite