diff --git a/autoarray/__init__.py b/autoarray/__init__.py index 81188792..d923d61a 100644 --- a/autoarray/__init__.py +++ b/autoarray/__init__.py @@ -84,7 +84,8 @@ from .structures.triangles.shape import Triangle from .structures.triangles.shape import Square from .structures.triangles.shape import Polygon -from .structures import decorators as grid_dec +from .structures import decorators as grid_dec # deprecated alias +from .structures import decorators from .structures.header import Header from .layout.region import Region1D from .layout.region import Region2D diff --git a/autoarray/mock.py b/autoarray/mock.py index 44220ae6..99a0cc10 100644 --- a/autoarray/mock.py +++ b/autoarray/mock.py @@ -20,3 +20,4 @@ from autoarray.structures.mock.mock_grid import MockMeshGrid from autoarray.structures.mock.mock_decorators import MockGrid1DLikeObj from autoarray.structures.mock.mock_decorators import MockGrid2DLikeObj +from autoarray.structures.mock.mock_decorators import MockTransformProfile diff --git a/autoarray/structures/decorators/abstract.py b/autoarray/structures/decorators/abstract.py index 5e4c86c4..e02e4e39 100644 --- a/autoarray/structures/decorators/abstract.py +++ b/autoarray/structures/decorators/abstract.py @@ -1,10 +1,6 @@ -from typing import Union - import numpy as np -from autoarray.mask.mask_1d import Mask1D from autoarray.mask.mask_2d import Mask2D -from autoarray.structures.grids.uniform_1d import Grid1D from autoarray.structures.grids.irregular_2d import Grid2DIrregular from autoarray.structures.grids.uniform_2d import Grid2D @@ -18,20 +14,16 @@ def __init__(self, func, obj, grid, xp=np, *args, **kwargs): This is used by the `to_array`, `to_grid` and `to_vector_yx` decorators to ensure that the input grid and output data structure are consistent. - There are three types of consistent data structures and therefore decorated function mappings: + There are two types of consistent data structures and therefore decorated function mappings: - Uniform: 2D structures defined on a uniform grid of data points, for example the `Array2D` and `Grid2D` objects. Both structures are defined according to a `Mask2D`, which the maker object ensures is passed through self consistently. - Irregular: 2D structures defined on an irregular grid of data points, for example an `ArrayIrregular` - and `Grid2DIrregular` objects. Neither structure is defined according to a mask and the maker sures the lack of + and `Grid2DIrregular` objects. Neither structure is defined according to a mask and the maker ensures the lack of a mask does not prevent the function from being evaluated. - - 1D: 1D structures defined on a 1D grid of data points, for example the `Array1D` and `Grid1D` objects. - These project the 1D grid to a 2D grid to ensure the function can be evaluated, and then deproject the 2D grid - back to a 1D grid to ensure the output data structure is consistent with the input grid. - Parameters ---------- func @@ -66,7 +58,7 @@ def _xp(self): return np @property - def mask(self) -> Union[Mask1D, Mask2D]: + def mask(self) -> Mask2D: return self.grid.mask @property @@ -79,30 +71,8 @@ def via_grid_2d(self, result): def via_grid_2d_irr(self, result): raise NotImplementedError - def via_grid_1d(self, result): - raise NotImplementedError - @property def evaluate_func(self): - """ - Evaluate the function that is being decorated, using the grid that is passed to the maker object when it is - initialized. - - In normal usage, the input grid is 2D and it is simply passed to the decorated function. - - However, if the input grid is 1D, the grid is projected to a 2D grid before being passed to the function. This - is because the function is expected to evaluate a 2D grid, and the maker object ensures that the function can - be evaluated by projecting the 1D grid to a 2D grid. - - Returns - ------- - The result of the function that is being decorated, which is the output data structure that is consistent with - the input grid. - """ - - if isinstance(self.grid, Grid1D): - grid = self.grid.grid_2d_radial_projected_from() - return self.func(self.obj, grid, self._xp, *self.args, **self.kwargs) return self.func(self.obj, self.grid, self._xp, *self.args, **self.kwargs) @property @@ -111,21 +81,17 @@ def result(self): The result of the function that is being decorated, which this function converts to the output data structure that is consistent with the input grid. - This function called one of three methods, depending on the type of the input grid: + This function calls one of two methods, depending on the type of the input grid: - `via_grid_2d`: If the input grid is a `Grid2D` object. - `via_grid_2d_irr`: If the input grid is a `Grid2DIrregular` object. - - `via_grid_1d`: If the input grid is a `Grid1D` object. - These functions are over written depending on whether the decorated function returns an array, grid or vector. - The over written functions are in the child classes `ArrayMaker`, `GridMaker` and `VectorYXMaker`. + If the input is a raw ndarray (e.g. numpy or JAX), the function result is returned unchanged. """ if isinstance(self.grid, Grid2D): return self.via_grid_2d(self.evaluate_func) elif isinstance(self.grid, Grid2DIrregular): return self.via_grid_2d_irr(self.evaluate_func) - elif isinstance(self.grid, Grid1D): - return self.via_grid_1d(self.evaluate_func) return self.evaluate_func diff --git a/autoarray/structures/decorators/to_array.py b/autoarray/structures/decorators/to_array.py index 2aaee8d6..aa7046b3 100644 --- a/autoarray/structures/decorators/to_array.py +++ b/autoarray/structures/decorators/to_array.py @@ -1,125 +1,49 @@ -import numpy as np -from functools import wraps - - -from typing import List, Union - -from autoarray.structures.arrays.irregular import ArrayIrregular -from autoarray.structures.arrays.uniform_1d import Array1D -from autoarray.structures.arrays.uniform_2d import Array2D -from autoarray.structures.decorators.abstract import AbstractMaker -from autoarray.structures.grids.uniform_1d import Grid1D -from autoarray.structures.grids.irregular_2d import Grid2DIrregular -from autoarray.structures.grids.uniform_2d import Grid2D - - -class ArrayMaker(AbstractMaker): - def via_grid_2d(self, result) -> Union[Array2D, List[Array2D]]: - """ - Convert the result of a decorated function which receives as input a `Grid2D` object to an `Array2D` object. - - If the result returns a list, a list of `Array2D` objects is returned. - - Parameters - ---------- - result - The input result (e.g. of a decorated function) that is converted to an Array2D or list of Array2D objects. - """ - - if not isinstance(result, list): - return Array2D(values=result, mask=self.mask) - return [Array2D(values=res, mask=self.mask) for res in result] - - def via_grid_2d_irr(self, result) -> Union[ArrayIrregular, List[ArrayIrregular]]: - """ - Convert the result of a decorated function which receives as input a `Grid2DIrregular` object to an `ArrayIrregular` - object. - - If the result returns a list, a list of `ArrayIrregular` objects is returned. - - Parameters - ---------- - result - The input result (e.g. of a decorated function) that is converted to an ArrayIrregular or list of - ArrayIrregular objects. - """ - if not isinstance(result, list): - return ArrayIrregular(values=result) - return [ArrayIrregular(values=res) for res in result] - - def via_grid_1d(self, result) -> Union[Array1D, List[Array1D]]: - """ - Convert the result of a decorated function which receives as input a `Grid1D` object to an `Array1D` object. - - If the result returns a list, a list of `Array1D` objects is returned. - - Parameters - ---------- - result - The input result (e.g. of a decorated function) that is converted to an Array1D or list of Array1D objects. - """ - if not isinstance(result, list): - return Array1D(values=result, mask=self.mask) - return [Array1D(values=res, mask=self.mask) for res in result] - - -def to_array(func): - """ - Homogenize the inputs and outputs of functions that take 1D or 2D grids of coordinates and return a 1D ndarray - which is converted to an `Array2D`, `ArrayIrregular` or `Array1D` object. - - Parameters - ---------- - func - A function which computes a set of values from a 1D or 2D grid of coordinates. - - Returns - ------- - A function that has its outputs homogenized to `Array2D`, `ArrayIrregular` or `Array1D` objects. - """ - - @wraps(func) - def wrapper( - obj: object, - grid: Union[np.ndarray, Grid2D, Grid2DIrregular, Grid1D], - xp=np, - *args, - **kwargs, - ) -> Union[np.ndarray, Array1D, Array2D, ArrayIrregular, List]: - """ - This decorator homogenizes the input of a "grid_like" 2D structure (`Grid2D`, `Grid2DIrregular` or `Grid1D`) - into a function which outputs an array-like structure (`Array2D`, `ArrayIrregular` or `Array1D`). - - It allows these classes to be interchangeably input into a function, such that the grid is used to evaluate - the function at every (y,x) coordinates of the grid using specific functionality of the input grid. - - The grid_like objects `Grid2D` and `Grid2DIrregular` are input into the function as a slimmed 2D ndarray array - of shape [total_coordinates, 2] where the second dimension stores the (y,x) - - There are three types of consistent data structures and therefore decorated function mappings: - - - Uniform (`Grid2D` -> `Array`): 2D structures defined on a uniform grid of data points. Both structures are - defined according to a `Mask2D`, which the maker object ensures is passed through self consistently. - - - Irregular (`Grid2DIrregular` -> `ArrayIrregular`: 2D structures defined on an irregular grid of data points, - Neither structure is defined according to a mask and the maker sures the lack of a mask does not prevent the - function from being evaluated. - - - 1D (`Grid1D` -> `Array1D`): 1D structures defined on a 1D grid of data points. These project the 1D grid - to a 2D grid to ensure the function can be evaluated, and then deproject the 2D grid back to a 1D grid to - ensure the output data structure is consistent with the input grid. - - Parameters - ---------- - obj - An object whose function uses grid_like inputs to compute quantities at every coordinate on the grid. - grid - A grid_like object of coordinates on which the function values are evaluated. - - Returns - ------- - The function values evaluated on the grid with the same structure as the input grid_like object. - """ - return ArrayMaker(func=func, obj=obj, grid=grid, xp=xp, *args, **kwargs).result - - return wrapper +import numpy as np +from functools import wraps +from typing import List, Union + +from autoarray.structures.arrays.irregular import ArrayIrregular +from autoarray.structures.arrays.uniform_2d import Array2D +from autoarray.structures.decorators.abstract import AbstractMaker +from autoarray.structures.grids.irregular_2d import Grid2DIrregular +from autoarray.structures.grids.uniform_2d import Grid2D + + +class ArrayMaker(AbstractMaker): + def via_grid_2d(self, result) -> Union[Array2D, List[Array2D]]: + if not isinstance(result, list): + return Array2D(values=result, mask=self.mask) + return [Array2D(values=res, mask=self.mask) for res in result] + + def via_grid_2d_irr(self, result) -> Union[ArrayIrregular, List[ArrayIrregular]]: + if not isinstance(result, list): + return ArrayIrregular(values=result) + return [ArrayIrregular(values=res) for res in result] + + +def to_array(func): + """ + Homogenize the inputs and outputs of functions that take 2D grids of coordinates and return a 1D ndarray + which is converted to an `Array2D` or `ArrayIrregular` object. + + Parameters + ---------- + func + A function which computes a set of values from a 2D grid of coordinates. + + Returns + ------- + A function that has its outputs homogenized to `Array2D` or `ArrayIrregular` objects. + """ + + @wraps(func) + def wrapper( + obj: object, + grid: Union[np.ndarray, Grid2D, Grid2DIrregular], + xp=np, + *args, + **kwargs, + ) -> Union[np.ndarray, Array2D, ArrayIrregular, List]: + return ArrayMaker(func=func, obj=obj, grid=grid, xp=xp, *args, **kwargs).result + + return wrapper diff --git a/autoarray/structures/decorators/to_grid.py b/autoarray/structures/decorators/to_grid.py index acad17bb..40c979e4 100644 --- a/autoarray/structures/decorators/to_grid.py +++ b/autoarray/structures/decorators/to_grid.py @@ -1,157 +1,63 @@ -from functools import wraps -import numpy as np -from typing import List, Union - -from autoarray.structures.decorators.abstract import AbstractMaker -from autoarray.structures.grids.uniform_1d import Grid1D -from autoarray.structures.grids.irregular_2d import Grid2DIrregular -from autoarray.structures.grids.uniform_2d import Grid2D - - -class GridMaker(AbstractMaker): - def via_grid_2d(self, result) -> Union[Grid2D, List[Grid2D]]: - """ - Convert the result of a decorated function which receives as input a `Grid2D` object to an `Grid2D` object. - - If the result returns a list, a list of `Grid2D` objects is returned. - - Parameters - ---------- - result - The input result (e.g. of a decorated function) that is converted to a Grid2D or list of Grid2D objects. - """ - if not isinstance(result, list): - try: - over_sampled = result.over_sampled - except AttributeError: - over_sampled = None - - try: - over_sampler = result.over_sampler - except AttributeError: - over_sampler = None - - return Grid2D( - values=result, - mask=self.mask, - over_sample_size=self.over_sample_size, - over_sampled=over_sampled, - over_sampler=over_sampler, - ) - - try: - grid_over_sampled_list = [res.over_sampled for res in result] - grid_over_sampler_list = [res.over_sampler for res in result] - except AttributeError: - grid_over_sampled_list = [None] * len(result) - grid_over_sampler_list = [None] * len(result) - - return [ - Grid2D( - values=res, - mask=self.mask, - over_sample_size=self.over_sample_size, - over_sampled=over_sampled, - over_sampler=over_sampler, - ) - for res, over_sampled, over_sampler in zip( - result, grid_over_sampled_list, grid_over_sampler_list - ) - ] - - def via_grid_2d_irr(self, result) -> Union[Grid2DIrregular, List[Grid2DIrregular]]: - """ - Convert the result of a decorated function which receives as input a `Grid2DIrregular` object to - an `Grid2DIrregular` object. - - If the result returns a list, a list of `Grid2DIrregular` objects is returned. - - Parameters - ---------- - result - The input result (e.g. of a decorated function) that is converted to an Grid2DIrregular or list of - `Grid2DIrregular` objects. - """ - if not isinstance(result, list): - return Grid2DIrregular(values=result) - return [Grid2DIrregular(values=res) for res in result] - - def via_grid_1d(self, result) -> Union[Grid2D, List[Grid2D]]: - """ - Convert the result of a decorated function which receives as input a `Grid1D` object to a `Grid2D` object - where a projection is performed from 1D to 2D before the function is evaluated. - - If the result returns a list, a list of `Grid2D` objects is returned. - - Parameters - ---------- - result - The input result (e.g. of a decorated function) that is converted to a Grid2D or list of Grid2D objects. - """ - if not isinstance(result, list): - return Grid2D(values=result, mask=self.mask.derive_mask.to_mask_2d) - return [ - Grid2D(values=res, mask=self.mask.derive_mask.to_mask_2d) for res in result - ] - - -def to_grid(func): - """ - Homogenize the inputs and outputs of functions that take 1D or 2D grids of coordinates and return a 1D ndarray - which is converted to an `Grid2D` or `Grid2DIrregular` object. - - Parameters - ---------- - func - A function which computes a set of values from a 1D or 2D grid of coordinates. - - Returns - ------- - A function that has its outputs homogenized to `Grid2D` or `Grid2DIrregular` objects. - """ - - @wraps(func) - def wrapper( - obj: object, - grid: Union[np.ndarray, Grid2D, Grid2DIrregular, Grid1D], - xp=np, - *args, - **kwargs, - ) -> Union[np.ndarray, Grid2D, Grid2DIrregular, List]: - """ - This decorator homogenizes the input of a "grid_like" 2D structure (`Grid2D`, `Grid2DIrregular` or `Grid1D`) - into a function which outputs a grid-like structure (`Grid2D` or `Grid2DIrregular`). - - It allows these classes to be interchangeably input into a function, such that the grid is used to evaluate - the function at every (y,x) coordinates of the grid using specific functionality of the input grid. - - The grid_like objects `Grid2D` and `Grid2DIrregular` are input into the function as a slimmed 2D ndarray array - of shape [total_coordinates, 2] where the second dimension stores the (y,x) - - There are three types of consistent data structures and therefore decorated function mappings: - - - Uniform (`Grid2D` -> `Grid2D`): 2D structures defined on a uniform grid of data points. Both structures are - defined according to a `Mask2D`, which the maker object ensures is passed through self consistently. - - - Irregular (`Grid2DIrregular` -> `Grid2DIrregular`: 2D structures defined on an irregular grid of data points, - Neither structure is defined according to a mask and the maker sures the lack of a mask does not prevent the - function from being evaluated. - - - 1D (`Grid1D` -> `Grid2D`): 1D structures defined on a 1D grid of data points. These project the 1D grid - to a 2D grid to ensure the function can be evaluated. - - Parameters - ---------- - obj - An object whose function uses grid_like inputs to compute quantities at every coordinate on the grid. - grid - A grid_like object of coordinates on which the function values are evaluated. - - Returns - ------- - The function values evaluated on the grid with the same structure as the input grid_like object. - """ - - return GridMaker(func=func, obj=obj, grid=grid, xp=xp, *args, **kwargs).result - - return wrapper +from functools import wraps +import numpy as np +from typing import List, Union + +from autoarray.structures.decorators.abstract import AbstractMaker +from autoarray.structures.grids.irregular_2d import Grid2DIrregular +from autoarray.structures.grids.uniform_2d import Grid2D + + +class GridMaker(AbstractMaker): + def via_grid_2d(self, result) -> Union[Grid2D, List[Grid2D]]: + if not isinstance(result, list): + return Grid2D( + values=result, + mask=self.mask, + over_sample_size=self.over_sample_size, + over_sampled=getattr(result, "over_sampled", None), + over_sampler=getattr(result, "over_sampler", None), + ) + + return [ + Grid2D( + values=res, + mask=self.mask, + over_sample_size=self.over_sample_size, + over_sampled=getattr(res, "over_sampled", None), + over_sampler=getattr(res, "over_sampler", None), + ) + for res in result + ] + + def via_grid_2d_irr(self, result) -> Union[Grid2DIrregular, List[Grid2DIrregular]]: + if not isinstance(result, list): + return Grid2DIrregular(values=result) + return [Grid2DIrregular(values=res) for res in result] + + +def to_grid(func): + """ + Homogenize the inputs and outputs of functions that take 2D grids of coordinates and return a 2D ndarray + which is converted to a `Grid2D` or `Grid2DIrregular` object. + + Parameters + ---------- + func + A function which computes a set of values from a 2D grid of coordinates. + + Returns + ------- + A function that has its outputs homogenized to `Grid2D` or `Grid2DIrregular` objects. + """ + + @wraps(func) + def wrapper( + obj: object, + grid: Union[np.ndarray, Grid2D, Grid2DIrregular], + xp=np, + *args, + **kwargs, + ) -> Union[np.ndarray, Grid2D, Grid2DIrregular, List]: + return GridMaker(func=func, obj=obj, grid=grid, xp=xp, *args, **kwargs).result + + return wrapper diff --git a/autoarray/structures/decorators/to_vector_yx.py b/autoarray/structures/decorators/to_vector_yx.py index 13ca2b21..0583ee63 100644 --- a/autoarray/structures/decorators/to_vector_yx.py +++ b/autoarray/structures/decorators/to_vector_yx.py @@ -1,114 +1,56 @@ -from functools import wraps -import numpy as np -from typing import List, Union - -from autoarray.structures.decorators.abstract import AbstractMaker -from autoarray.structures.grids.uniform_1d import Grid1D -from autoarray.structures.grids.irregular_2d import Grid2DIrregular -from autoarray.structures.grids.uniform_2d import Grid2D -from autoarray.structures.vectors.irregular import VectorYX2DIrregular -from autoarray.structures.vectors.uniform import VectorYX2D - - -class VectorYXMaker(AbstractMaker): - def via_grid_2d(self, result) -> Union[VectorYX2D, List[VectorYX2D]]: - """ - Convert the result of a decorated function which receives as input a `Grid2D` object to a `VectorYX2D` object. - - If the result returns a list, a list of `VectorYX2D` objects is returned. - - Parameters - ---------- - result - The input result (e.g. of a decorated function) that is converted to an VectorYX2D or list of VectorYX2D - objects. - """ - if not isinstance(result, list): - return VectorYX2D(values=result, grid=self.grid, mask=self.grid.mask) - return [ - VectorYX2D(values=res, grid=self.grid, mask=self.grid.mask) - for res in result - ] - - def via_grid_2d_irr( - self, result - ) -> Union[VectorYX2DIrregular, List[VectorYX2DIrregular]]: - """ - Convert the result of a decorated function which receives as input a `VectorYX2DIrregular` object to - an `VectorYX2DIrregular` object. - - If the result returns a list, a list of `VectorYX2DIrregular` objects is returned. - - Parameters - ---------- - result - The input result (e.g. of a decorated function) that is converted to an VectorYX2DIrregular or list of - VectorYX2DIrregular objects. - """ - if not isinstance(result, list): - return VectorYX2DIrregular(values=result, grid=self.grid) - return [VectorYX2DIrregular(values=res, grid=self.grid) for res in result] - - -def to_vector_yx(func): - """ - Homogenize the inputs and outputs of functions that take 1D or 2D grids of coordinates and return a 1D ndarray - which is converted to an `VectorYX2D` or `VectorYX2DIrregular` object. - - Parameters - ---------- - func - A function which computes a set of values from a 1D or 2D grid of coordinates. - - Returns - ------- - A function that has its outputs homogenized to `VectorYX2D` or `VectorYX2DIrregular` objects. - """ - - @wraps(func) - def wrapper( - obj: object, - grid: Union[np.ndarray, Grid2D, Grid2DIrregular, Grid1D], - xp=np, - *args, - **kwargs, - ) -> Union[np.ndarray, VectorYX2D, VectorYX2DIrregular, List]: - """ - This decorator homogenizes the input of a "grid_like" 2D structure (`Grid2D`, `Grid2DIrregular` or `Grid1D`) - into a function which outputs a vector-like structure (`VectorYX2D` or `VectorYX2DIrregular`). - - It allows these classes to be interchangeably input into a function, such that the grid is used to evaluate - the function at every (y,x) coordinates of the grid using specific functionality of the input grid. - - The grid_like objects `Grid2D` and `Grid2DIrregular` are input into the function as a slimmed 2D ndarray array - of shape [total_coordinates, 2] where the second dimension stores the (y,x) - - There are three types of consistent data structures and therefore decorated function mappings: - - - Uniform (`Grid2D` -> `VectorYX2D`): 2D structures defined on a uniform grid of data points. Both structures are - defined according to a `Mask2D`, which the maker object ensures is passed through self consistently. - - - Irregular (`Grid2DIrregular` -> `VectorYX2DIrregular`: 2D structures defined on an irregular grid of data points, - Neither structure is defined according to a mask and the maker sures the lack of a mask does not prevent the - function from being evaluated. - - - 1D (`Grid1D` -> `Grid2D`): 1D structures defined on a 1D grid of data points. These are not applicable - for vector-like structures and are not supported by this decorator. - - Parameters - ---------- - obj - An object whose function uses grid_like inputs to compute quantities at every coordinate on the grid. - grid - A grid_like object of coordinates on which the function values are evaluated. - - Returns - ------- - The function values evaluated on the grid with the same structure as the input grid_like object. - """ - - return VectorYXMaker( - func=func, obj=obj, grid=grid, xp=xp, *args, **kwargs - ).result - - return wrapper +from functools import wraps +import numpy as np +from typing import List, Union + +from autoarray.structures.decorators.abstract import AbstractMaker +from autoarray.structures.grids.irregular_2d import Grid2DIrregular +from autoarray.structures.grids.uniform_2d import Grid2D +from autoarray.structures.vectors.irregular import VectorYX2DIrregular +from autoarray.structures.vectors.uniform import VectorYX2D + + +class VectorYXMaker(AbstractMaker): + def via_grid_2d(self, result) -> Union[VectorYX2D, List[VectorYX2D]]: + if not isinstance(result, list): + return VectorYX2D(values=result, grid=self.grid, mask=self.grid.mask) + return [ + VectorYX2D(values=res, grid=self.grid, mask=self.grid.mask) + for res in result + ] + + def via_grid_2d_irr( + self, result + ) -> Union[VectorYX2DIrregular, List[VectorYX2DIrregular]]: + if not isinstance(result, list): + return VectorYX2DIrregular(values=result, grid=self.grid) + return [VectorYX2DIrregular(values=res, grid=self.grid) for res in result] + + +def to_vector_yx(func): + """ + Homogenize the inputs and outputs of functions that take 2D grids of coordinates and return a 2D ndarray + which is converted to a `VectorYX2D` or `VectorYX2DIrregular` object. + + Parameters + ---------- + func + A function which computes a set of values from a 2D grid of coordinates. + + Returns + ------- + A function that has its outputs homogenized to `VectorYX2D` or `VectorYX2DIrregular` objects. + """ + + @wraps(func) + def wrapper( + obj: object, + grid: Union[np.ndarray, Grid2D, Grid2DIrregular], + xp=np, + *args, + **kwargs, + ) -> Union[np.ndarray, VectorYX2D, VectorYX2DIrregular, List]: + return VectorYXMaker( + func=func, obj=obj, grid=grid, xp=xp, *args, **kwargs + ).result + + return wrapper diff --git a/autoarray/structures/decorators/transform.py b/autoarray/structures/decorators/transform.py index 4571b043..86fbfbab 100644 --- a/autoarray/structures/decorators/transform.py +++ b/autoarray/structures/decorators/transform.py @@ -2,7 +2,6 @@ import numpy as np from typing import Union -from autoarray.structures.grids.uniform_1d import Grid1D from autoarray.structures.grids.irregular_2d import Grid2DIrregular from autoarray.structures.grids.uniform_2d import Grid2D @@ -64,31 +63,11 @@ def decorator(func): @wraps(func) def wrapper( obj: object, - grid: Union[np.ndarray, Grid2D, Grid2DIrregular, Grid1D], + grid: Union[np.ndarray, Grid2D, Grid2DIrregular], xp=np, *args, **kwargs, ) -> Union[np.ndarray, Grid2D, Grid2DIrregular]: - """ - This decorator checks whether the input grid has been transformed to the reference frame of the class - that owns the function. If it has not been transformed, it is transformed. - - The transform state is tracked via the ``is_transformed`` property on the grid object itself. - When a decorated function calls another decorated function with the same (already-transformed) - grid, the flag prevents the grid from being transformed a second time. - - Parameters - ---------- - obj - An object whose function uses grid_like inputs to compute quantities at every coordinate on the grid. - grid - The (y, x) coordinates in the original reference frame of the grid. - - Returns - ------- - A grid_like object whose coordinates may be transformed. - """ - if not getattr(grid, "is_transformed", False): transformed_grid = obj.transformed_to_reference_frame_grid_from( grid, xp, **kwargs diff --git a/autoarray/structures/mock/mock_decorators.py b/autoarray/structures/mock/mock_decorators.py index 8ca7790d..4b21c8c6 100644 --- a/autoarray/structures/mock/mock_decorators.py +++ b/autoarray/structures/mock/mock_decorators.py @@ -81,6 +81,25 @@ def ndarray_2d_yx_from(profile, grid, *args, **kwargs): return 2.0 * grid +class MockTransformProfile: + def __init__(self, centre=(1.0, 2.0)): + self.centre = centre + + def transformed_to_reference_frame_grid_from(self, grid, xp=None, **kwargs): + return grid - np.array(self.centre) + + def rotated_grid_from_reference_frame_from(self, grid, xp=None, **kwargs): + return -1.0 * grid + + @decorators.transform + def scalar_from(self, grid, xp=None, *args, **kwargs): + return np.sum(grid, axis=1) + + @decorators.transform(rotate_back=True) + def vector_from(self, grid, xp=None, *args, **kwargs): + return 2.0 * grid + + class MockGrid1DLikeObj: def __init__(self, centre=(0.0, 0.0), angle=0.0): self.centre = centre @@ -104,6 +123,10 @@ def ndarray_1d_from(self, grid, *args, **kwargs): """ return np.ones(shape=grid.shape[0]) + @decorators.to_array + def ndarray_1d_no_oversample_from(self, grid, *args, **kwargs): + return np.ones(shape=grid.shape[0]) + @decorators.to_grid def ndarray_2d_from(self, grid, *args, **kwargs): """ @@ -114,6 +137,10 @@ def ndarray_2d_from(self, grid, *args, **kwargs): """ return np.multiply(2.0, grid.array) + @decorators.to_grid + def ndarray_2d_raw_from(self, grid, *args, **kwargs): + return np.multiply(2.0, grid) + @decorators.to_vector_yx def ndarray_yx_2d_from(self, grid, *args, **kwargs): """ diff --git a/test_autoarray/structures/decorators/test_to_array.py b/test_autoarray/structures/decorators/test_to_array.py index bbda1f6e..74c90b1d 100644 --- a/test_autoarray/structures/decorators/test_to_array.py +++ b/test_autoarray/structures/decorators/test_to_array.py @@ -3,22 +3,6 @@ import autoarray as aa -def test__in_grid_1d__out_ndarray_1d_list(): - mask = aa.Mask1D(mask=[True, False, False, True], pixel_scales=(1.0,)) - - grid_1d = aa.Grid1D.from_mask(mask=mask) - - obj = aa.m.MockGrid2DLikeObj() - - ndarray_1d_list = obj.ndarray_1d_list_from(grid=grid_1d) - - assert isinstance(ndarray_1d_list[0], aa.Array1D) - assert (ndarray_1d_list[0].native == np.array([[0.0, 1.0, 1.0, 0.0]])).all() - - assert isinstance(ndarray_1d_list[1], aa.Array1D) - assert (ndarray_1d_list[1].native == np.array([[0.0, 2.0, 2.0, 0.0]])).all() - - def test__in_grid_2d__out_ndarray_1d_list(): mask = aa.Mask2D( mask=[ @@ -83,3 +67,15 @@ def test__in_grid_2d_irregular__out_ndarray_1d_list(): assert ndarray_1d_list[0].in_list == [1.0, 1.0, 1.0] assert ndarray_1d_list[1].in_list == [2.0, 2.0, 2.0] + + +def test__in_ndarray__out_ndarray(): + obj = aa.m.MockGrid2DLikeObj() + + grid = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) + + result = obj.ndarray_1d_no_oversample_from(grid=grid) + + assert isinstance(result, np.ndarray) + assert not isinstance(result, aa.Array2D) + assert not isinstance(result, aa.ArrayIrregular) diff --git a/test_autoarray/structures/decorators/test_to_grid.py b/test_autoarray/structures/decorators/test_to_grid.py index 2e8b1be2..05573c0d 100644 --- a/test_autoarray/structures/decorators/test_to_grid.py +++ b/test_autoarray/structures/decorators/test_to_grid.py @@ -4,41 +4,6 @@ import autoarray as aa -def test__in_grid_1d__out_ndarray_2d(): - mask_1d = aa.Mask1D(mask=[True, False, False, True], pixel_scales=(1.0,)) - - grid_1d = aa.Grid1D.from_mask(mask=mask_1d) - - obj = aa.m.MockGrid2DLikeObj() - - ndarray_2d = obj.ndarray_2d_from(grid=grid_1d) - - assert isinstance(ndarray_2d, aa.Grid2D) - assert ndarray_2d.native == pytest.approx( - np.array([[[0.0, 0.0], [0.0, -1.0], [0.0, 1.0], [0.0, 0.0]]]), abs=1.0e-4 - ) - - -def test__in_dgrid_1d__out_ndarray_2d_list(): - mask = aa.Mask1D(mask=[True, False, False, True], pixel_scales=(1.0,)) - - grid_1d = aa.Grid1D.from_mask(mask=mask) - - obj = aa.m.MockGrid2DLikeObj() - - ndarray_2d_list = obj.ndarray_2d_list_from(grid=grid_1d) - - assert isinstance(ndarray_2d_list[0], aa.Grid2D) - assert ndarray_2d_list[0].native == pytest.approx( - np.array([[[0.0, 0.0], [0.0, -0.5], [0.0, 0.5], [0.0, 0.0]]]), abs=1.0e-4 - ) - - assert isinstance(ndarray_2d_list[1], aa.Grid2D) - assert ndarray_2d_list[1].native == pytest.approx( - np.array([[[0.0, 0.0], [0.0, -1.0], [0.0, 1.0], [0.0, 0.0]]]), abs=1.0e-4 - ) - - def test__in_grid_2d__out_ndarray_2d(): mask = aa.Mask2D( mask=[ @@ -133,3 +98,15 @@ def test__in_grid_2d_irregular__out_ndarray_2d_list(): assert ndarray_2d_list[0].in_list == [(1.0, 2.0), (3.0, 4.0), (5.0, 6.0)] assert ndarray_2d_list[1].in_list == [(2.0, 4.0), (6.0, 8.0), (10.0, 12.0)] + + +def test__in_ndarray__out_ndarray(): + obj = aa.m.MockGrid2DLikeObj() + + grid = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) + + result = obj.ndarray_2d_raw_from(grid=grid) + + assert isinstance(result, np.ndarray) + assert not isinstance(result, aa.Grid2D) + assert not isinstance(result, aa.Grid2DIrregular) diff --git a/test_autoarray/structures/decorators/test_to_vector_yx.py b/test_autoarray/structures/decorators/test_to_vector_yx.py index d9614c0b..03a7d8cf 100644 --- a/test_autoarray/structures/decorators/test_to_vector_yx.py +++ b/test_autoarray/structures/decorators/test_to_vector_yx.py @@ -101,3 +101,15 @@ def test__in_grid_2d_irregular__out_ndarray_yx_2d_list(): assert isinstance(ndarray_yx_2d_list[1], aa.VectorYX2DIrregular) assert ndarray_yx_2d_list[1].in_list == [(2.0, 4.0), (6.0, 8.0), (10.0, 12.0)] + + +def test__in_ndarray__out_ndarray(): + obj = aa.m.MockGrid2DLikeObj() + + grid = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) + + result = obj.ndarray_yx_2d_from(grid=grid) + + assert isinstance(result, np.ndarray) + assert not isinstance(result, aa.VectorYX2D) + assert not isinstance(result, aa.VectorYX2DIrregular) diff --git a/test_autoarray/structures/decorators/test_transform.py b/test_autoarray/structures/decorators/test_transform.py new file mode 100644 index 00000000..8e705ace --- /dev/null +++ b/test_autoarray/structures/decorators/test_transform.py @@ -0,0 +1,63 @@ +import numpy as np +import pytest + +import autoarray as aa + + +def _make_grid_2d(): + mask = aa.Mask2D( + mask=[ + [True, True, True, True], + [True, False, False, True], + [True, False, False, True], + [True, True, True, True], + ], + pixel_scales=(1.0, 1.0), + ) + return aa.Grid2D.from_mask(mask=mask) + + +def test__transform_applied(): + obj = aa.m.MockTransformProfile(centre=(1.0, 2.0)) + + grid = _make_grid_2d() + + result = obj.scalar_from(grid=grid) + + # Grid coords are approximately [(0.5,-0.5),(0.5,0.5),(-0.5,-0.5),(-0.5,0.5)] + # After transform (subtract centre [1.0, 2.0]): + # [0.5-1.0, -0.5-2.0] = [-0.5, -2.5] -> sum = -3.0 + # [0.5-1.0, 0.5-2.0] = [-0.5, -1.5] -> sum = -2.0 + # [-0.5-1.0,-0.5-2.0] = [-1.5, -2.5] -> sum = -4.0 + # [-0.5-1.0, 0.5-2.0] = [-1.5, -1.5] -> sum = -3.0 + assert result == pytest.approx(np.array([-3.0, -2.0, -4.0, -3.0])) + + +def test__already_transformed_skipped(): + obj = aa.m.MockTransformProfile(centre=(1.0, 2.0)) + + grid = _make_grid_2d() + grid.is_transformed = True + + result = obj.scalar_from(grid=grid) + + # No transformation applied — sum of raw coords + # [(0.5,-0.5),(0.5,0.5),(-0.5,-0.5),(-0.5,0.5)] + # sums: [0.0, 1.0, -1.0, 0.0] + assert result == pytest.approx(np.array([0.0, 1.0, -1.0, 0.0])) + + +def test__rotate_back(): + obj = aa.m.MockTransformProfile(centre=(1.0, 2.0)) + + grid = _make_grid_2d() + + result = obj.vector_from(grid=grid) + + # Grid transformed (subtract centre), then doubled, then negated (rotate_back mock) + # [-0.5,-2.5]*2 = [-1.0,-5.0] -> negated = [1.0, 5.0] + # [-0.5,-1.5]*2 = [-1.0,-3.0] -> negated = [1.0, 3.0] + # [-1.5,-2.5]*2 = [-3.0,-5.0] -> negated = [3.0, 5.0] + # [-1.5,-1.5]*2 = [-3.0,-3.0] -> negated = [3.0, 3.0] + expected = np.array([[1.0, 5.0], [1.0, 3.0], [3.0, 5.0], [3.0, 3.0]]) + assert result == pytest.approx(expected)