Skip to content

Commit

Permalink
First pass at simplifying explore
Browse files Browse the repository at this point in the history
  • Loading branch information
robbibt committed Jan 23, 2024
1 parent d34b4ed commit 5f2bb5b
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 54 deletions.
18 changes: 15 additions & 3 deletions odc/geo/_compress.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,27 @@
}


def _verify_can_compress(xx: xr.DataArray):
"""Returns error if array dimensions are not suitable for compress"""
if xx.ndim > 2:
xx = xx.squeeze()

if xx.ndim not in (2, 3):
raise ValueError(
f"Expected a 2 or 3 dimensional array; got {xx.ndim} dimensions {xx.dims}."
)


def _compress_image(im: np.ndarray, driver="PNG", **opts) -> bytes:
if im.ndim > 2:
im = np.squeeze(im)

if im.ndim == 3:
h, w, nc = im.shape
bands = np.transpose(im, axes=(2, 0, 1)) # Y,X,B -> B,Y,X
elif im.ndim == 2:
else:
(h, w), nc = im.shape, 1
bands = im.reshape(nc, h, w)
else:
raise ValueError(f"Expect 2 or 3 dimensional array got: {im.ndim}")

rio_opts = {
"width": w,
Expand Down Expand Up @@ -76,6 +85,9 @@ def compress(
Pixel value to use for transparent pixels, useful for jpeg output.
"""
# Raise error early if xx has unsuitable dims
_verify_can_compress(xx)

fmt = "png"
opts = {}
if len(args) >= 1:
Expand Down
100 changes: 50 additions & 50 deletions odc/geo/_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def add_to(
name: Optional[str] = None,
fmt: str = "png",
max_size: int = 4096,
resampling: str = "nearest",
# jpeg options:
transparent_pixel: Optional[Tuple[int, int, int]] = None,
# RGB conversion parameters
Expand All @@ -70,27 +71,41 @@ def add_to(
**kw,
) -> Any:
"""
Add image to a map.
Add image to an interactive map.
If map is not supplied, image data url and bounds are returned instead.
:param xx: array to display
:param xx:
The :py:class:`~xarray.DataArray` to display
:param map:
Map object, :py:mod:`folium` and :py:mod:`ipyleaflet` are understood, can be ``None``.
Map object, :py:mod:`folium` and :py:mod:`ipyleaflet` are
understood; can also be ``None`` which will return an image data
url and bounds instead.
:param name:
The name of the layer as it will appear in :py:mod:`folium` and :py:mod:`ipyleaflet`
Layer Controls. The default ``None`` will use the input array name (e.g. ``xx.name``) if it exists.
:param fmt: compress image format, defaults to "png", can be "webp", "jpeg" as well.
The name of the layer as it will appear in :py:mod:`folium` and
:py:mod:`ipyleaflet` Layer Controls. The default ``None`` will
use the input array name (e.g. ``xx.name``) if it exists.
:param fmt:
Compress image format. Defaults to "png"; also supports "webp",
"jpeg".
:param max_size:
If longest dimension is bigger than this, shrink it down before compression, defaults to 4096
:param transparent_pixel: Replace transparent pixels with this value, needed for "jpeg".
If longest dimension is bigger than this, shrink it down before
compression; defaults to 4096.
:param resampling:
Custom resampling method to use when reprojecting ``xx`` to the
map CRS; defaults to "nearest".
:param transparent_pixel:
Replace transparent pixels with this value, needed for "jpeg".
:param cmap: If supplied array is not RGB use this colormap to turn it into one
:param clip: When converting to RGB clip input values to fit ``cmap``.
:param cmap:
If supplied array is not RGB use this colormap to turn it into one.
:param clip:
When converting to RGB clip input values to fit ``cmap``.
:param vmin: Used with matplotlib colormaps
:param vmax: Used with matplotlib colormaps
:param robust: Used with matplotlib colormaps, ``vmin=2%,vmax=98%``
:param robust: Used with matplotlib colormaps, ``vmin=2%, vmax=98%``
:raises ValueError: when map object is not understood
:return: ImageLayer that was added to a map
Expand Down Expand Up @@ -135,7 +150,7 @@ def add_to(
gbox = gbox.zoom_to(max_size)

if gbox is not gbox0:
xx = xx.odc.reproject(gbox)
xx = xx.odc.reproject(gbox, resampling=resampling)

if not is_rgb(xx):
xx = colorize(xx, cmap=cmap, clip=clip, vmin=vmin, vmax=vmax, robust=robust)
Expand Down Expand Up @@ -169,7 +184,6 @@ def explore(
layer_control: bool = True,
resampling: str = "nearest",
map_kwds: Optional[Dict[str, Any]] = None,
reproject_kwds: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> Any:
"""
Expand Down Expand Up @@ -219,67 +233,53 @@ def explore(
map CRS; defaults to "nearest".
:param map_kwds:
Additional keyword arguments to pass to ``folium.Map()``.
:param reproject_kwds:
Additional keyword arguments to pass to ``.odc.reproject()``.
:param \**kwargs:
Additional keyword arguments to pass to ``.odc.add_to()``.
:return: A :py:mod:`folium` map containing the plotted xarray data.
"""
assert have.folium
if not have.folium:
raise ModuleNotFoundError(
"'folium' is required but not installed. "
"Please install it before using `.explore()`."
)

from folium import Map, LayerControl

# Empty kwargs by default
if map_kwds is None:
map_kwds = {}
if reproject_kwds is None:
reproject_kwds = {}

# Update any supplied kwargs with custom params
map_kwds = {} if map_kwds is None else map_kwds
kwargs.update(cmap=cmap, vmin=vmin, vmax=vmax, robust=robust)
map_kwds.update(tiles=tiles, attr=attr)
reproject_kwds.update(resampling=resampling)

# If input is a dataset, convert to an RGBA array
if isinstance(xx, xr.Dataset):
try:
xx = xx.odc.to_rgba(bands=bands, vmin=vmin, vmax=vmax)
except ValueError as e:
raise ValueError(
f"Unable to automatically guess RGB colours ({e}). "
f"Manually specify bands to plot using the `bands` "
"parameter, e.g. `ds.odc.explore(bands=['a', 'b', 'c'])`"
) from e

# Squeeze to remove single length dims
xx = xx.squeeze()

# Check if correct number of dimensions and raise error early
# to avoid needlessly running expensive reprojection
is_2d = not is_rgb(xx) and xx.ndim == 2
is_3d_rgba = is_rgb(xx) and xx.ndim == 3
if not (is_2d or is_3d_rgba):
if "Must specify clamp" in str(e):
raise e
else:
ValueError(
f"Unable to automatically guess RGB colours ({e}). "
f"Manually specify bands to plot using the `bands` "
"parameter, e.g. `ds.odc.explore(bands=['a', 'b', 'c'])`"
)

# Create folium Map if required
if map is None:
map = Map(**map_kwds)

# Add to map and raise a friendly error if data has unsuitable dims
try:
xx.odc.add_to(map, **kwargs)
except ValueError as e:
raise ValueError(
"Only 2D single-band (x, y) or 3D multi-band (x, y, band) "
"arrays are supported by `.explore()`. Please reduce the "
"dimensions in your array, for example by using `.isel()` "
"or `.sel()`: `da.isel(time=0).odc.explore()`."
)

# Create folium Map if required
if map is None:
map = Map(**map_kwds)

# If necessary, reproject to map CRS
_crs = map_crs(map)
layer_name = xx.name # copy out name as it is lost in reprojection
if _crs != xx.odc.crs:
xx = xx.odc.reproject(_crs, **reproject_kwds)

# Add data to map
xx.odc.add_to(map, name=layer_name, **kwargs)

# Zoom map to extent of data
map.fit_bounds(xx.odc.map_bounds())

Expand Down
2 changes: 1 addition & 1 deletion odc/geo/_rgba.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def to_rgba(

if vmax is None:
if is_dask:
raise ValueError("Must specify clamp for Dask inputs")
raise ValueError("Must specify clamp for Dask inputs (vmax, vmin)")
_vmin, vmax = _auto_guess_clamp(ds[list(bands)])
vmin = _vmin if vmin is None else vmin

Expand Down

0 comments on commit 5f2bb5b

Please sign in to comment.