csiro-coasts / emsarray

xarray extension that supports EMS model formats
BSD 3-Clause "New" or "Revised" License
13 stars 2 forks source link

Add `Convention.from_array()` method, similar to `Convention.wind()` #131

Closed mx-moth closed 4 months ago

mx-moth commented 6 months ago

Add a new abstract method to Convention, with a DimensionConvention implementation of:

def from_array(
    self,
    array: numpy.ndarray,
    grid_kind: Optional[GridKind],
) -> xarray.DataArray:
    if grid_kind is None:
        grid_kind = self.default_grid_kind

    expected_shape = self.grid_shape[grid_kind]
    expected_size = self.grid_size[grid_kind]

    if array.shape[-len(expected_shape):] == expected_shape:
        # The array is already in the correct shape, wrap it in a DataArray with the right dimensions
        # TODO Handle setting coordinates?
        dims = ['dim{i}' for i in range(len(array.shape) - len(expected_size))] + list(self.grid_dimensions[grid_kind])
        return xarray.DataArray(array, dims=dims)

    if array.shape[-1] == expected_size:
        # The last axis of the array is of the correct size for a flattened array
        return self.wind(xarray.DataArray(array), axis=-1)

    raise ValueError(
        f"The final axes of the array were neither the expected shape {expected_shape} "
        f"nor a flattened array of the expected size {expected_size}"
    )

Usage:

data_array = dataset.ems.from_array(array)

This will handle the logic of wrapping an arbitrary numpy array in an xarray.DataArray that follows the convention. This is useful if you've done some logic to manipulate a Convention DataArray, or computed some values using the geometry of the dataset, and now have a plain numpy array of the correct shape. The alternate approach is to do:

data_array = dataset.ems.wind(xarray.DataArray(array.flatten()))

This approach saves a .flatten() call and supports extra preceding dimensions.

Questions:

mx-moth commented 4 months ago

The issue of disambiguating leading dimensions means this wasn't possible to implement nicely, and without that it doesn't provide anything extra over Convention.wind()