Source code for movement.transforms
"""Transform and add unit attributes to xarray.DataArray datasets."""
import numpy as np
import xarray as xr
from numpy.typing import ArrayLike
from movement.validators.arrays import validate_dims_coords
[docs]
def scale(
data: xr.DataArray,
factor: ArrayLike | float = 1.0,
space_unit: str | None = None,
) -> xr.DataArray:
"""Scale data by a given factor with an optional unit.
Parameters
----------
data : xarray.DataArray
The input data to be scaled.
factor : ArrayLike or float
The scaling factor to apply to the data. If factor is a scalar (a
single float), the data array is uniformly scaled by the same factor.
If factor is an object that can be converted to a 1D numpy array (e.g.
a list of floats), the length of the resulting array must match the
length of data array's space dimension along which it will be
broadcasted.
space_unit : str or None
The unit of the scaled data stored as a property in
xarray.DataArray.attrs['space_unit']. In case of the default (``None``)
the ``space_unit`` attribute is dropped.
Returns
-------
xarray.DataArray
The scaled data array.
Notes
-----
When scale is used multiple times on the same xarray.DataArray,
xarray.DataArray.attrs["space_unit"] is overwritten each time or is dropped
if ``None`` is passed by default or explicitly.
"""
if len(data.coords["space"]) == 2:
validate_dims_coords(data, {"space": ["x", "y"]})
else:
validate_dims_coords(data, {"space": ["x", "y", "z"]})
if not np.isscalar(factor):
factor = np.array(factor).squeeze()
if factor.ndim != 1:
raise ValueError(
"Factor must be an object that can be converted to a 1D numpy"
f" array, got {factor.ndim}D"
)
elif factor.shape != data.space.values.shape:
raise ValueError(
f"Factor shape {factor.shape} does not match the shape "
f"of the space dimension {data.space.values.shape}"
)
else:
factor_dims = [1] * data.ndim # 1s array matching data dimensions
factor_dims[data.get_axis_num("space")] = factor.shape[0]
factor = factor.reshape(factor_dims)
scaled_data = data * factor
if space_unit is not None:
scaled_data.attrs["space_unit"] = space_unit
elif space_unit is None:
scaled_data.attrs.pop("space_unit", None)
return scaled_data