Source code for movement.roi.conditions
"""Functions for computing condition arrays involving RoIs."""
from collections import defaultdict
from collections.abc import Sequence
import numpy as np
import xarray as xr
from movement.roi.base import BaseRegionOfInterest
[docs]
def compute_region_occupancy(
data,
regions: Sequence[BaseRegionOfInterest],
) -> xr.DataArray:
"""Return a condition array indicating if points were inside regions.
The function returns a boolean DataArray where each element indicates
whether a point in the input ``data`` lies within the corresponding RoIs
in ``regions``. The original dimensions of ``data`` are preserved, except
for the ``space`` dimension which is replaced by the ``region``
dimension. The ``region`` dimension has a number of elements equal to
the number of RoIs in the ``regions`` argument and it's coordinate names
correspond to the names of the given RoIs.
Parameters
----------
data : xarray.DataArray
Spatial data to check for inclusion within the ``regions``. Must be
compatible with the ``data`` argument to :func:`contains_point\
<movement.roi.base.BaseRegionOfInterest.contains_point>`.
regions : Sequence[BaseRegionOfInterest]
Regions of Interest that the points in ``data`` will be checked
against, to see if they lie inside.
Returns
-------
xarray.DataArray
A boolean ``DataArray`` providing occupancy information.
Examples
--------
>>> import numpy as np
>>> import xarray as xr
>>> from movement.roi import PolygonOfInterest, compute_region_occupancy
>>> square = PolygonOfInterest(
... [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)], name="square"
... )
>>> triangle = PolygonOfInterest(
... [(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)], name="triangle"
... )
>>> data = xr.DataArray(
... data=np.array([[0.25, 0.25], [0.75, 0.75]]),
... dims=["time", "space"],
... coords={"space": ["x", "y"]},
... )
>>> occupancies = compute_region_occupancy(data, [square, triangle])
>>> occupancies.sel(region="square").values
np.array([True, True])
>>> occupancies.sel(region="triangle").values
np.array([True, False])
Notes
-----
When RoIs in ``regions`` have identical names, a suffix
will be appended to their name in the form of "_X", where "X" is a number
starting from 0. These numbers are zero-padded depending on the maximum
number of regions with identical names (e.g. if there are 100 RoIs with the
same name, "00" will be appended to the first of them)
Regions with unique names will retain their original name as their
corresponding coordinate name.
"""
number_of_times_name_appears: defaultdict[str, int] = defaultdict(int)
for r in regions:
number_of_times_name_appears[r.name] += 1
duplicate_names_max_chars = {
key: int(np.ceil(np.log10(value)).item())
for key, value in number_of_times_name_appears.items()
if value > 1
}
duplicate_names_used: defaultdict[str, int] = defaultdict(int)
occupancies = {}
for r in regions:
name = r.name
if name in duplicate_names_max_chars:
name_suffix = str(duplicate_names_used[name]).zfill(
duplicate_names_max_chars[name]
)
duplicate_names_used[name] += 1
name = f"{name}_{name_suffix}"
occupancies[name] = r.contains_point(data)
return xr.concat(occupancies.values(), dim="region").assign_coords(
region=list(occupancies.keys())
)