import numpy as np
from typing import Tuple, Union, Optional

from autoarray.structures.arrays.two_d import array_2d_util
from autoarray.geometry import geometry_util
from autoarray import numba_util
from autoarray.mask import mask_2d_util


@numba_util.jit()
def grid_2d_centre_from(grid_2d_slim: np.ndarray) -> Tuple[float, float]:
    """
    Returns the centre of a grid from a 1D grid.

    Parameters
    ----------
    grid_2d_slim
        The 1D grid of values which are mapped to a 2D array.

    Returns
    -------
    (float, float)
        The (y,x) central coordinates of the grid.
    """
    centre_y = (np.max(grid_2d_slim[:, 0]) + np.min(grid_2d_slim[:, 0])) / 2.0
    centre_x = (np.max(grid_2d_slim[:, 1]) + np.min(grid_2d_slim[:, 1])) / 2.0
    return centre_y, centre_x


@numba_util.jit()
def grid_2d_slim_via_mask_from(
    mask_2d: np.ndarray,
    pixel_scales: Union[float, Tuple[float, float]],
    sub_size: int,
    origin: Tuple[float, float] = (0.0, 0.0),
) -> np.ndarray:
    """
    For a sub-grid, every unmasked pixel of its 2D mask with shape (total_y_pixels, total_x_pixels) is divided into
    a finer uniform grid of shape (total_y_pixels*sub_size, total_x_pixels*sub_size). This routine computes the (y,x)
    scaled coordinates a the centre of every sub-pixel defined by this 2D mask array.

    The sub-grid is returned on an array of shape (total_unmasked_pixels*sub_size**2, 2). y coordinates are
    stored in the 0 index of the second dimension, x coordinates in the 1 index. Masked coordinates are therefore
    removed and not included in the slimmed grid.

    Grid2D are defined from the top-left corner, where the first unmasked sub-pixel corresponds to index 0.
    Sub-pixels that are part of the same mask array pixel are indexed next to one another, such that the second
    sub-pixel in the first pixel has index 1, its next sub-pixel has index 2, and so forth.

    Parameters
    ----------
    mask_2d
        A 2D array of bools, where `False` values are unmasked and therefore included as part of the calculated
        sub-grid.
    pixel_scales
        The (y,x) scaled units to pixel units conversion factor of the 2D mask array.
    sub_size
        The size of the sub-grid that each pixel of the 2D mask array is divided into.
    origin : (float, flloat)
        The (y,x) origin of the 2D array, which the sub-grid is shifted around.

    Returns
    -------
    ndarray
        A slimmed sub grid of (y,x) scaled coordinates at the centre of every pixel unmasked pixel on the 2D mask
        array. The sub grid array has dimensions (total_unmasked_pixels*sub_size**2, 2).

    Examples
    --------
    mask = np.array([[True, False, True],
                     [False, False, False]
                     [True, False, True]])
    grid_slim = grid_2d_slim_via_mask_from(mask=mask, pixel_scales=(0.5, 0.5), sub_size=1, origin=(0.0, 0.0))
    """

    total_sub_pixels = mask_2d_util.total_sub_pixels_2d_from(mask_2d, sub_size)

    grid_slim = np.zeros(shape=(total_sub_pixels, 2))

    centres_scaled = geometry_util.central_scaled_coordinate_2d_from(
        shape_native=mask_2d.shape, pixel_scales=pixel_scales, origin=origin
    )

    sub_index = 0

    y_sub_half = pixel_scales[0] / 2
    y_sub_step = pixel_scales[0] / (sub_size)

    x_sub_half = pixel_scales[1] / 2
    x_sub_step = pixel_scales[1] / (sub_size)

    for y in range(mask_2d.shape[0]):
        for x in range(mask_2d.shape[1]):

            if not mask_2d[y, x]:

                y_scaled = (y - centres_scaled[0]) * pixel_scales[0]
                x_scaled = (x - centres_scaled[1]) * pixel_scales[1]

                for y1 in range(sub_size):
                    for x1 in range(sub_size):

                        grid_slim[sub_index, 0] = -(
                            y_scaled - y_sub_half + y1 * y_sub_step + (y_sub_step / 2.0)
                        )
                        grid_slim[sub_index, 1] = (
                            x_scaled - x_sub_half + x1 * x_sub_step + (x_sub_step / 2.0)
                        )
                        sub_index += 1

    return grid_slim


def grid_2d_via_mask_from(
    mask_2d: np.ndarray,
    pixel_scales: Union[float, Tuple[float, float]],
    sub_size: int,
    origin: Tuple[float, float] = (0.0, 0.0),
) -> np.ndarray:
    """
    For a sub-grid, every unmasked pixel of its 2D mask with shape (total_y_pixels, total_x_pixels) is divided into a
    finer uniform grid of shape (total_y_pixels*sub_size, total_x_pixels*sub_size). This routine computes the (y,x)
    scaled coordinates at the centre of every sub-pixel defined by this 2D mask array.

    The sub-grid is returned in its native dimensions with shape (total_y_pixels*sub_size, total_x_pixels*sub_size).
    y coordinates are stored in the 0 index of the second dimension, x coordinates in the 1 index. Masked pixels are
    given values (0.0, 0.0).

    Grids are defined from the top-left corner, where the first unmasked sub-pixel corresponds to index 0.
    Sub-pixels that are part of the same mask array pixel are indexed next to one another, such that the second
    sub-pixel in the first pixel has index 1, its next sub-pixel has index 2, and so forth.

    Parameters
    ----------
    mask_2d
        A 2D array of bools, where `False` values are unmasked and therefore included as part of the calculated
        sub-grid.
    pixel_scales
        The (y,x) scaled units to pixel units conversion factor of the 2D mask array.
    sub_size
        The size of the sub-grid that each pixel of the 2D mask array is divided into.
    origin : (float, flloat)
        The (y,x) origin of the 2D array, which the sub-grid is shifted around.

    Returns
    -------
    ndarray
        A sub grid of (y,x) scaled coordinates at the centre of every pixel unmasked pixel on the 2D mask
        array. The sub grid array has dimensions (total_y_pixels*sub_size, total_x_pixels*sub_size).

    Examples
    --------
    mask = np.array([[True, False, True],
                     [False, False, False]
                     [True, False, True]])
    grid_2d = grid_2d_via_mask_from(mask=mask, pixel_scales=(0.5, 0.5), sub_size=1, origin=(0.0, 0.0))
    """

    grid_2d_slim = grid_2d_slim_via_mask_from(
        mask_2d=mask_2d, pixel_scales=pixel_scales, sub_size=sub_size, origin=origin
    )

    return grid_2d_native_from(
        grid_2d_slim=grid_2d_slim, mask_2d=mask_2d, sub_size=sub_size
    )


def grid_2d_slim_via_shape_native_from(
    shape_native: Tuple[int, int],
    pixel_scales: Union[float, Tuple[float, float]],
    sub_size: int,
    origin: Tuple[float, float] = (0.0, 0.0),
) -> np.ndarray:
    """
    For a sub-grid, every unmasked pixel of its 2D mask with shape (total_y_pixels, total_x_pixels) is divided into a
    finer uniform grid of shape (total_y_pixels*sub_size, total_x_pixels*sub_size). This routine computes the (y,x)
    scaled coordinates at the centre of every sub-pixel defined by this 2D mask array.

    The sub-grid is returned in its slimmed dimensions with shape (total_pixels**2*sub_size**2, 2). y coordinates are
    stored in the 0 index of the second dimension, x coordinates in the 1 index.

    Grid2D are defined from the top-left corner, where the first sub-pixel corresponds to index [0,0].
    Sub-pixels that are part of the same mask array pixel are indexed next to one another, such that the second
    sub-pixel in the first pixel has index 1, its next sub-pixel has index 2, and so forth.

    Parameters
    ----------
    shape_native
        The (y,x) shape of the 2D array the sub-grid of coordinates is computed for.
    pixel_scales
        The (y,x) scaled units to pixel units conversion factor of the 2D mask array.
    sub_size
        The size of the sub-grid that each pixel of the 2D mask array is divided into.
    origin
        The (y,x) origin of the 2D array, which the sub-grid is shifted around.

    Returns
    -------
    ndarray
        A sub grid of (y,x) scaled coordinates at the centre of every pixel unmasked pixel on the 2D mask
        array. The sub grid is slimmed and has dimensions (total_unmasked_pixels*sub_size**2, 2).

    Examples
    --------
    mask = np.array([[True, False, True],
                     [False, False, False]
                     [True, False, True]])
    grid_2d_slim = grid_2d_slim_via_shape_native_from(shape_native=(3,3), pixel_scales=(0.5, 0.5), sub_size=2, origin=(0.0, 0.0))
    """
    return grid_2d_slim_via_mask_from(
        mask_2d=np.full(fill_value=False, shape=shape_native),
        pixel_scales=pixel_scales,
        sub_size=sub_size,
        origin=origin,
    )


def grid_2d_via_shape_native_from(
    shape_native: Tuple[int, int],
    pixel_scales: Union[float, Tuple[float, float]],
    sub_size: int,
    origin: Tuple[float, float] = (0.0, 0.0),
) -> np.ndarray:
    """
    For a sub-grid, every unmasked pixel of its 2D mask with shape (total_y_pixels, total_x_pixels) is divided
    into a finer uniform grid of shape (total_y_pixels*sub_size, total_x_pixels*sub_size). This routine computes
    the (y,x) scaled coordinates at the centre of every sub-pixel defined by this 2D mask array.

    The sub-grid is returned in its native dimensions with shape (total_y_pixels*sub_size, total_x_pixels*sub_size).
    y coordinates are stored in the 0 index of the second dimension, x coordinates in the 1 index.

    Grids are defined from the top-left corner, where the first sub-pixel corresponds to index [0,0].
    Sub-pixels that are part of the same mask array pixel are indexed next to one another, such that the second
    sub-pixel in the first pixel has index 1, its next sub-pixel has index 2, and so forth.

    Parameters
    ----------
    shape_native
        The (y,x) shape of the 2D array the sub-grid of coordinates is computed for.
    pixel_scales
        The (y,x) scaled units to pixel units conversion factor of the 2D mask array.
    sub_size
        The size of the sub-grid that each pixel of the 2D mask array is divided into.
    origin : (float, flloat)
        The (y,x) origin of the 2D array, which the sub-grid is shifted around.

    Returns
    -------
    ndarray
        A sub grid of (y,x) scaled coordinates at the centre of every pixel unmasked pixel on the 2D mask
        array. The sub grid array has dimensions (total_y_pixels*sub_size, total_x_pixels*sub_size).

    Examples
    --------
    grid_2d = grid_2d_via_shape_native_from(shape_native=(3, 3), pixel_scales=(1.0, 1.0), sub_size=2, origin=(0.0, 0.0))
    """
    return grid_2d_via_mask_from(
        mask_2d=np.full(fill_value=False, shape=shape_native),
        pixel_scales=pixel_scales,
        sub_size=sub_size,
        origin=origin,
    )


@numba_util.jit()
def grid_scaled_2d_slim_radial_projected_from(
    extent: np.ndarray,
    centre: Tuple[float, float],
    pixel_scales: Union[float, Tuple[float, float]],
    sub_size: int,
    shape_slim: Optional[int] = 0,
) -> np.ndarray:
    """
    Determine a projected radial grid of points from a 2D region of coordinates defined by an
    extent [xmin, xmax, ymin, ymax] and with a (y,x) centre. This functions operates as follows:

    1) Given the region defined by the extent [xmin, xmax, ymin, ymax], the algorithm finds the longest 1D distance of
    the 4 paths from the (y,x) centre to the edge of the region (e.g. following the positive / negative y and x axes).

    2) Use the pixel-scale corresponding to the direction chosen (e.g. if the positive x-axis was the longest, the
    pixel_scale in the x dimension is used).

    3) Determine the number of pixels between the centre and the edge of the region using the longest path between the
    two chosen above.

    4) Create a (y,x) grid of radial points where all points are at the centre's y value = 0.0 and the x values iterate
    from the centre in increasing steps of the pixel-scale.

    5) Rotate these radial coordinates by the input `angle` clockwise.

    A schematric is shown below:

    -------------------
    |                 |
    |<- - -  - ->x    | x = centre
    |                 | <-> = longest radial path from centre to extent edge
    |                 |
    -------------------

    Using the centre x above, this function finds the longest radial path to the edge of the extent window.

    The returned `grid_radii` represents a radial set of points that in 1D sample the 2D grid outwards from its centre.
    This grid stores the radial coordinates as (y,x) values (where all y values are the same) as opposed to a 1D data
    structure so that it can be used in functions which require that a 2D grid structure is input.

    Parameters
    ----------
    extent
        The extent of the grid the radii grid is computed using, with format [xmin, xmax, ymin, ymax]
    centre : (float, flloat)
        The (y,x) central coordinate which the radial grid is traced outwards from.
    pixel_scales
        The (y,x) scaled units to pixel units conversion factor of the 2D mask array.
    sub_size
        The size of the sub-grid that each pixel of the 2D mask array is divided into.
    shape_slim
        Manually choose the shape of the 1D projected grid that is returned. If 0, the border based on the 2D grid is
        used (due to numba None cannot be used as a default value).

    Returns
    -------
    ndarray
        A radial set of points sampling the longest distance from the centre to the edge of the extent in along the
        positive x-axis.
    """

    distance_to_positive_x = extent[1] - centre[1]
    distance_to_positive_y = extent[3] - centre[0]
    distance_to_negative_x = centre[1] - extent[0]
    distance_to_negative_y = centre[0] - extent[2]

    scaled_distance = max(
        [
            distance_to_positive_x,
            distance_to_positive_y,
            distance_to_negative_x,
            distance_to_negative_y,
        ]
    )

    if (scaled_distance == distance_to_positive_y) or (
        scaled_distance == distance_to_negative_y
    ):
        pixel_scale = pixel_scales[0]
    else:
        pixel_scale = pixel_scales[1]

    if shape_slim == 0:
        shape_slim = sub_size * int((scaled_distance / pixel_scale)) + 1

    grid_scaled_2d_slim_radii = np.zeros((shape_slim, 2))

    grid_scaled_2d_slim_radii[:, 0] += centre[0]

    radii = centre[1]

    for slim_index in range(shape_slim):

        grid_scaled_2d_slim_radii[slim_index, 1] = radii
        radii += pixel_scale / sub_size

    return grid_scaled_2d_slim_radii


@numba_util.jit()
def grid_pixels_2d_slim_from(
    grid_scaled_2d_slim: np.ndarray,
    shape_native: Tuple[int, int],
    pixel_scales: Union[float, Tuple[float, float]],
    origin: Tuple[float, float] = (0.0, 0.0),
) -> np.ndarray:
    """
    Convert a slimmed grid of 2d (y,x) scaled coordinates to a slimmed grid of 2d (y,x) pixel coordinate values. Pixel
    coordinates are returned as floats such that they include the decimal offset from each pixel's top-left corner
    relative to the input scaled coordinate.

    The input and output grids are both slimmed and therefore shape (total_pixels, 2).

    The pixel coordinate origin is at the top left corner of the grid, such that the pixel [0,0] corresponds to
    the highest (most positive) y scaled coordinate and lowest (most negative) x scaled coordinate on the gird.

    The scaled grid is defined by an origin and coordinates are shifted to this origin before computing their
    1D grid pixel coordinate values.

    Parameters
    ----------
    grid_scaled_2d_slim: np.ndarray
        The slimmed grid of 2D (y,x) coordinates in scaled units which are converted to pixel value coordinates.
    shape_native
        The (y,x) shape of the original 2D array the scaled coordinates were computed on.
    pixel_scales
        The (y,x) scaled units to pixel units conversion factor of the original 2D array.
    origin : (float, flloat)
        The (y,x) origin of the grid, which the scaled grid is shifted to.

    Returns
    -------
    ndarray
        A slimmed grid of 2D (y,x) pixel-value coordinates with dimensions (total_pixels, 2).

    Examples
    --------
    grid_scaled_2d_slim = np.array([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0]])
    grid_pixels_2d_slim = grid_scaled_2d_slim_from(grid_scaled_2d_slim=grid_scaled_2d_slim, shape=(2,2),
                                                           pixel_scales=(0.5, 0.5), origin=(0.0, 0.0))
    """

    grid_pixels_2d_slim = np.zeros((grid_scaled_2d_slim.shape[0], 2))

    centres_scaled = geometry_util.central_scaled_coordinate_2d_from(
        shape_native=shape_native, pixel_scales=pixel_scales, origin=origin
    )

    for slim_index in range(grid_scaled_2d_slim.shape[0]):

        grid_pixels_2d_slim[slim_index, 0] = (
            (-grid_scaled_2d_slim[slim_index, 0] / pixel_scales[0])
            + centres_scaled[0]
            + 0.5
        )
        grid_pixels_2d_slim[slim_index, 1] = (
            (grid_scaled_2d_slim[slim_index, 1] / pixel_scales[1])
            + centres_scaled[1]
            + 0.5
        )

    return grid_pixels_2d_slim


@numba_util.jit()
def grid_pixel_centres_2d_slim_from(
    grid_scaled_2d_slim: np.ndarray,
    shape_native: Tuple[int, int],
    pixel_scales: Union[float, Tuple[float, float]],
    origin: Tuple[float, float] = (0.0, 0.0),
) -> np.ndarray:
    """
    Convert a slimmed grid of 2D (y,x) scaled coordinates to a slimmed grid of 2D (y,x) pixel values. Pixel coordinates
    are returned as integers such that they map directly to the pixel they are contained within.

    The input and output grids are both slimmed and therefore shape (total_pixels, 2).

    The pixel coordinate origin is at the top left corner of the grid, such that the pixel [0,0] corresponds to
    the highest (most positive) y scaled coordinate and lowest (most negative) x scaled coordinate on the gird.

    The scaled coordinate grid is defined by the class attribute origin, and coordinates are shifted to this
    origin before computing their 1D grid pixel indexes.

    Parameters
    ----------
    grid_scaled_2d_slim: np.ndarray
        The slimmed grid of 2D (y,x) coordinates in scaled units which is converted to pixel indexes.
    shape_native
        The (y,x) shape of the original 2D array the scaled coordinates were computed on.
    pixel_scales
        The (y,x) scaled units to pixel units conversion factor of the original 2D array.
    origin : (float, flloat)
        The (y,x) origin of the grid, which the scaled grid is shifted

    Returns
    -------
    ndarray
        A slimmed grid of 2D (y,x) pixel indexes with dimensions (total_pixels, 2).

    Examples
    --------
    grid_scaled_2d_slim = np.array([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0]])
    grid_pixels_2d_slim = grid_scaled_2d_slim_from(grid_scaled_2d_slim=grid_scaled_2d_slim, shape=(2,2),
                                                           pixel_scales=(0.5, 0.5), origin=(0.0, 0.0))
    """

    grid_pixels_2d_slim = np.zeros((grid_scaled_2d_slim.shape[0], 2))

    centres_scaled = geometry_util.central_scaled_coordinate_2d_from(
        shape_native=shape_native, pixel_scales=pixel_scales, origin=origin
    )

    for slim_index in range(grid_scaled_2d_slim.shape[0]):

        grid_pixels_2d_slim[slim_index, 0] = int(
            (-grid_scaled_2d_slim[slim_index, 0] / pixel_scales[0])
            + centres_scaled[0]
            + 0.5
        )
        grid_pixels_2d_slim[slim_index, 1] = int(
            (grid_scaled_2d_slim[slim_index, 1] / pixel_scales[1])
            + centres_scaled[1]
            + 0.5
        )

    return grid_pixels_2d_slim


@numba_util.jit()
def grid_pixel_indexes_2d_slim_from(
    grid_scaled_2d_slim: np.ndarray,
    shape_native: Tuple[int, int],
    pixel_scales: Union[float, Tuple[float, float]],
    origin: Tuple[float, float] = (0.0, 0.0),
) -> np.ndarray:
    """
    Convert a slimmed grid of 2D (y,x) scaled coordinates to a slimmed grid of pixel indexes. Pixel coordinates are
    returned as integers such that they are the pixel from the top-left of the 2D grid going rights and then downwards.

    The input and output grids are both slimmed and have shapes (total_pixels, 2) and (total_pixels,).

    For example:

    The pixel at the top-left, whose native index is [0,0], corresponds to slimmed pixel index 0.
    The fifth pixel on the top row, whose native index is [0,5], corresponds to slimmed pixel index 4.
    The first pixel on the second row, whose native index is [0,1], has slimmed pixel index 10 if a row has 10 pixels.

    The scaled coordinate grid is defined by the class attribute origin, and coordinates are shifted to this
    origin before computing their 1D grid pixel indexes.

    The input and output grids are both of shape (total_pixels, 2).

    Parameters
    ----------
    grid_scaled_2d_slim: np.ndarray
        The slimmed grid of 2D (y,x) coordinates in scaled units which is converted to slimmed pixel indexes.
    shape_native
        The (y,x) shape of the original 2D array the scaled coordinates were computed on.
    pixel_scales
        The (y,x) scaled units to pixel units conversion factor of the original 2D array.
    origin : (float, flloat)
        The (y,x) origin of the grid, which the scaled grid is shifted.

    Returns
    -------
    ndarray
        A grid of slimmed pixel indexes with dimensions (total_pixels,).

    Examples
    --------
    grid_scaled_2d_slim = np.array([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0]])
    grid_pixel_indexes_2d_slim = grid_pixel_indexes_2d_slim_from(grid_scaled_2d_slim=grid_scaled_2d_slim, shape=(2,2),
                                                           pixel_scales=(0.5, 0.5), origin=(0.0, 0.0))
    """

    grid_pixels_2d_slim = grid_pixel_centres_2d_slim_from(
        grid_scaled_2d_slim=grid_scaled_2d_slim,
        shape_native=shape_native,
        pixel_scales=pixel_scales,
        origin=origin,
    )

    grid_pixel_indexes_2d_slim = np.zeros(grid_pixels_2d_slim.shape[0])

    for slim_index in range(grid_pixels_2d_slim.shape[0]):

        grid_pixel_indexes_2d_slim[slim_index] = int(
            grid_pixels_2d_slim[slim_index, 0] * shape_native[1]
            + grid_pixels_2d_slim[slim_index, 1]
        )

    return grid_pixel_indexes_2d_slim


@numba_util.jit()
def grid_scaled_2d_slim_from(
    grid_pixels_2d_slim: np.ndarray,
    shape_native: Tuple[int, int],
    pixel_scales: Union[float, Tuple[float, float]],
    origin: Tuple[float, float] = (0.0, 0.0),
) -> np.ndarray:
    """
    Convert a slimmed grid of 2D (y,x) pixel coordinates to a slimmed grid of 2D (y,x) scaled values.

    The input and output grids are both slimmed and therefore shape (total_pixels, 2).

    The pixel coordinate origin is at the top left corner of the grid, such that the pixel [0,0] corresponds to
    the highest (most positive) y scaled coordinate and lowest (most negative) x scaled coordinate on the gird.

    The scaled coordinate origin is defined by the class attribute origin, and coordinates are shifted to this
    origin after computing their values from the 1D grid pixel indexes.

    Parameters
    ----------
    grid_pixels_2d_slim: np.ndarray
        The slimmed grid of (y,x) coordinates in pixel values which is converted to scaled coordinates.
    shape_native
        The (y,x) shape of the original 2D array the scaled coordinates were computed on.
    pixel_scales
        The (y,x) scaled units to pixel units conversion factor of the original 2D array.
    origin : (float, flloat)
        The (y,x) origin of the grid, which the scaled grid is shifted.

    Returns
    -------
    ndarray
        A slimmed grid of 2d scaled coordinates with dimensions (total_pixels, 2).

    Examples
    --------
    grid_pixels_2d_slim = np.array([[0,0], [0,1], [1,0], [1,1])
    grid_pixels_2d_slim = grid_scaled_2d_slim_from(grid_pixels_2d_slim=grid_pixels_2d_slim, shape=(2,2),
                                                           pixel_scales=(0.5, 0.5), origin=(0.0, 0.0))
    """

    grid_scaled_2d_slim = np.zeros((grid_pixels_2d_slim.shape[0], 2))

    centres_scaled = geometry_util.central_scaled_coordinate_2d_from(
        shape_native=shape_native, pixel_scales=pixel_scales, origin=origin
    )

    for slim_index in range(grid_scaled_2d_slim.shape[0]):

        grid_scaled_2d_slim[slim_index, 0] = (
            -(grid_pixels_2d_slim[slim_index, 0] - centres_scaled[0] - 0.5)
            * pixel_scales[0]
        )
        grid_scaled_2d_slim[slim_index, 1] = (
            grid_pixels_2d_slim[slim_index, 1] - centres_scaled[1] - 0.5
        ) * pixel_scales[1]

    return grid_scaled_2d_slim


@numba_util.jit()
def grid_pixel_centres_2d_from(
    grid_scaled_2d: np.ndarray,
    shape_native: Tuple[int, int],
    pixel_scales: Union[float, Tuple[float, float]],
    origin: Tuple[float, float] = (0.0, 0.0),
) -> np.ndarray:
    """
    Convert a native grid of 2D (y,x) scaled coordinates to a native grid of 2D (y,x) pixel values. Pixel coordinates
    are returned as integers such that they map directly to the pixel they are contained within.

    The input and output grids are both native resolution and therefore have shape (y_pixels, x_pixels, 2).

    The pixel coordinate origin is at the top left corner of the grid, such that the pixel [0,0] corresponds to
    the highest (most positive) y scaled coordinate and lowest (most negative) x scaled coordinate on the gird.

    The scaled coordinate grid is defined by the class attribute origin, and coordinates are shifted to this
    origin before computing their 1D grid pixel indexes.

    Parameters
    ----------
    grid_scaled_2d: np.ndarray
        The native grid of 2D (y,x) coordinates in scaled units which is converted to pixel indexes.
    shape_native
        The (y,x) shape of the original 2D array the scaled coordinates were computed on.
    pixel_scales
        The (y,x) scaled units to pixel units conversion factor of the original 2D array.
    origin : (float, flloat)
        The (y,x) origin of the grid, which the scaled grid is shifted

    Returns
    -------
    ndarray
        A native grid of 2D (y,x) pixel indexes with dimensions (y_pixels, x_pixels, 2).

    Examples
    --------
    grid_scaled_2d = np.array([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0]])
    grid_pixel_centres_2d = grid_pixel_centres_2d_from(grid_scaled_2d=grid_scaled_2d, shape=(2,2),
                                                           pixel_scales=(0.5, 0.5), origin=(0.0, 0.0))
    """

    grid_pixels_2d = np.zeros((grid_scaled_2d.shape[0], grid_scaled_2d.shape[1], 2))

    centres_scaled = geometry_util.central_scaled_coordinate_2d_from(
        shape_native=shape_native, pixel_scales=pixel_scales, origin=origin
    )

    for y in range(grid_scaled_2d.shape[0]):
        for x in range(grid_scaled_2d.shape[1]):
            grid_pixels_2d[y, x, 0] = int(
                (-grid_scaled_2d[y, x, 0] / pixel_scales[0]) + centres_scaled[0] + 0.5
            )
            grid_pixels_2d[y, x, 1] = int(
                (grid_scaled_2d[y, x, 1] / pixel_scales[1]) + centres_scaled[1] + 0.5
            )

    return grid_pixels_2d


@numba_util.jit()
def relocated_grid_via_jit_from(grid, border_grid):
    """
    Relocate the coordinates of a grid to its border if they are outside the border, where the border is
    defined as all pixels at the edge of the grid's mask (see *mask._border_1d_indexes*).

    This is performed as follows:

    1: Use the mean value of the grid's y and x coordinates to determine the origin of the grid.
    2: Compute the radial distance of every grid coordinate from the origin.
    3: For every coordinate, find its nearest pixel in the border.
    4: Determine if it is outside the border, by comparing its radial distance from the origin to its paired
    border pixel's radial distance.
    5: If its radial distance is larger, use the ratio of radial distances to move the coordinate to the
    border (if its inside the border, do nothing).

    The method can be used on uniform or irregular grids, however for irregular grids the border of the
    'image-plane' mask is used to define border pixels.

    Parameters
    ----------
    grid : Grid2D
        The grid (uniform or irregular) whose pixels are to be relocated to the border edge if outside it.
    border_grid : Grid2D
        The grid of border (y,x) coordinates.
    """

    grid_relocated = np.zeros(grid.shape)
    grid_relocated[:, :] = grid[:, :]

    border_origin = np.zeros(2)
    border_origin[0] = np.mean(border_grid[:, 0])
    border_origin[1] = np.mean(border_grid[:, 1])
    border_grid_radii = np.sqrt(
        np.add(
            np.square(np.subtract(border_grid[:, 0], border_origin[0])),
            np.square(np.subtract(border_grid[:, 1], border_origin[1])),
        )
    )
    border_min_radii = np.min(border_grid_radii)

    grid_radii = np.sqrt(
        np.add(
            np.square(np.subtract(grid[:, 0], border_origin[0])),
            np.square(np.subtract(grid[:, 1], border_origin[1])),
        )
    )

    for pixel_index in range(grid.shape[0]):

        if grid_radii[pixel_index] > border_min_radii:

            closest_pixel_index = np.argmin(
                np.square(grid[pixel_index, 0] - border_grid[:, 0])
                + np.square(grid[pixel_index, 1] - border_grid[:, 1])
            )

            move_factor = (
                border_grid_radii[closest_pixel_index] / grid_radii[pixel_index]
            )

            if move_factor < 1.0:

                grid_relocated[pixel_index, :] = (
                    move_factor * (grid[pixel_index, :] - border_origin[:])
                    + border_origin[:]
                )

    return grid_relocated


@numba_util.jit()
def furthest_grid_2d_slim_index_from(
    grid_2d_slim: np.ndarray, slim_indexes: np.ndarray, coordinate: Tuple[float, float]
) -> int:

    distance_to_centre = 0.0

    for slim_index in slim_indexes:

        y = grid_2d_slim[slim_index, 0]
        x = grid_2d_slim[slim_index, 1]
        distance_to_centre_new = (x - coordinate[1]) ** 2 + (y - coordinate[0]) ** 2

        if distance_to_centre_new >= distance_to_centre:
            distance_to_centre = distance_to_centre_new
            furthest_grid_2d_slim_index = slim_index

    return furthest_grid_2d_slim_index


def grid_2d_slim_from(
    grid_2d_native: np.ndarray, mask: np.ndarray, sub_size: int
) -> np.ndarray:
    """
    For a native 2D grid and mask of shape [total_y_pixels, total_x_pixels, 2], map the values of all unmasked
    pixels to a slimmed grid of shape [total_unmasked_pixels, 2].

    The pixel coordinate origin is at the top left corner of the native grid and goes right-wards and downwards, such
    that for an grid of shape (3,3) where all pixels are unmasked:

    - pixel [0,0] of the 2D grid will correspond to index 0 of the 1D grid.
    - pixel [0,1] of the 2D grid will correspond to index 1 of the 1D grid.
    - pixel [1,0] of the 2D grid will correspond to index 4 of the 1D grid.

    Parameters
    ----------
    grid_2d_native : ndarray
        The native grid of (y,x) values which are mapped to the slimmed grid.
    mask_2d
        A 2D array of bools, where `False` values mean unmasked and are included in the mapping.
    sub_size
        The size (sub_size x sub_size) of each unmasked pixels sub-array.

    Returns
    -------
    ndarray
        A 1D grid of values mapped from the 2D grid with dimensions (total_unmasked_pixels).
    """

    grid_1d_slim_y = array_2d_util.array_2d_slim_from(
        array_2d_native=grid_2d_native[:, :, 0], mask_2d=mask, sub_size=sub_size
    )

    grid_1d_slim_x = array_2d_util.array_2d_slim_from(
        array_2d_native=grid_2d_native[:, :, 1], mask_2d=mask, sub_size=sub_size
    )

    return np.stack((grid_1d_slim_y, grid_1d_slim_x), axis=-1)


def grid_2d_native_from(
    grid_2d_slim: np.ndarray, mask_2d: np.ndarray, sub_size: int
) -> np.ndarray:
    """
    For a slimmed 2D grid of shape [total_unmasked_pixels, 2], that was computed by extracting the unmasked values
    from a native 2D grid of shape [total_y_pixels, total_x_pixels, 2], map the slimmed grid's coordinates back to the
    native 2D grid where masked values are set to zero.

    This uses a 1D array 'slim_to_native' where each index gives the 2D pixel indexes of the grid's native unmasked
    pixels, for example:

    - If slim_to_native[0] = [0,0], the first value of the 1D array maps to the pixels [0,0,:] of the native 2D grid.
    - If slim_to_native[1] = [0,1], the second value of the 1D array maps to the pixels [0,1,:] of the native 2D grid.
    - If slim_to_native[4] = [1,1], the fifth value of the 1D array maps to the pixels [1,1,:] of the native 2D grid.

    Parameters
    ----------
    grid_2d_slim
        The (y,x) values of the slimmed 2D grid which are mapped to the native 2D grid.
    mask_2d
        A 2D array of bools, where `False` values mean unmasked and are included in the mapping.
    sub_size
        The size (sub_size x sub_size) of each unmasked pixels sub-array.

    Returns
    -------
    ndarray
        A NumPy array of shape [total_y_pixels, total_x_pixels, 2] corresponding to the (y,x) values of the native 2D
        mapped from the slimmed grid.
    """

    grid_2d_native_y = array_2d_util.array_2d_native_from(
        array_2d_slim=grid_2d_slim[:, 0], mask_2d=mask_2d, sub_size=sub_size
    )

    grid_2d_native_x = array_2d_util.array_2d_native_from(
        array_2d_slim=grid_2d_slim[:, 1], mask_2d=mask_2d, sub_size=sub_size
    )

    return np.stack((grid_2d_native_y, grid_2d_native_x), axis=-1)


@numba_util.jit()
def grid_2d_slim_upscaled_from(
    grid_slim: np.ndarray,
    upscale_factor: int,
    pixel_scales: Union[float, Tuple[float, float]],
) -> np.ndarray:
    """
    From an input slimmed 2D grid, return an upscaled slimmed 2D grid where (y,x) coordinates are added at an
    upscaled resolution to each grid coordinate, analogous to a sub-grid.

    Parameters
    ----------
    grid_slim
        The slimmed grid of (y,x) coordinates over which a square uniform grid is overlaid.
    upscale_factor
        The upscaled resolution at which the new grid coordinates are computed.
    pixel_scales
        The pixel scale of the uniform grid that laid over the irregular grid of (y,x) coordinates.
    """

    grid_2d_slim_upscaled = np.zeros(
        shape=(grid_slim.shape[0] * upscale_factor ** 2, 2)
    )

    upscale_index = 0

    y_upscale_half = pixel_scales[0] / 2
    y_upscale_step = pixel_scales[0] / upscale_factor

    x_upscale_half = pixel_scales[1] / 2
    x_upscale_step = pixel_scales[1] / upscale_factor

    for slim_index in range(grid_slim.shape[0]):

        y_grid = grid_slim[slim_index, 0]
        x_grid = grid_slim[slim_index, 1]

        for y in range(upscale_factor):
            for x in range(upscale_factor):

                grid_2d_slim_upscaled[upscale_index, 0] = (
                    y_grid
                    + y_upscale_half
                    - y * y_upscale_step
                    - (y_upscale_step / 2.0)
                )
                grid_2d_slim_upscaled[upscale_index, 1] = (
                    x_grid
                    - x_upscale_half
                    + x * x_upscale_step
                    + (x_upscale_step / 2.0)
                )

                upscale_index += 1

    return grid_2d_slim_upscaled


def grid_2d_of_points_within_radius(
    radius: float, centre: Tuple[float, float], grid_2d: np.ndarray
):
    y_inside = []
    x_inside = []

    for i in range(len(grid_2d[:, 0])):
        if (grid_2d[i, 0] - centre[0]) ** 2 + (
            grid_2d[i, 1] - centre[1]
        ) ** 2 > radius ** 2:
            y_inside.append(grid_2d[i, 0])
            x_inside.append(grid_2d[i, 1])

    return np.asarray(y_inside, x_inside)


def compute_polygon_area(points):

    x = points[:, 1]
    y = points[:, 0]

    return 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))

[((252, 268), 'autoarray.numba_util.jit', 'numba_util.jit', ([], {}), '()\n', (266, 268), False, 'from autoarray import numba_util\n'), ((824, 840), 'autoarray.numba_util.jit', 'numba_util.jit', ([], {}), '()\n', (838, 840), False, 'from autoarray import numba_util\n'), ((11036, 11052), 'autoarray.numba_util.jit', 'numba_util.jit', ([], {}), '()\n', (11050, 11052), False, 'from autoarray import numba_util\n'), ((14926, 14942), 'autoarray.numba_util.jit', 'numba_util.jit', ([], {}), '()\n', (14940, 14942), False, 'from autoarray import numba_util\n'), ((17600, 17616), 'autoarray.numba_util.jit', 'numba_util.jit', ([], {}), '()\n', (17614, 17616), False, 'from autoarray import numba_util\n'), ((20218, 20234), 'autoarray.numba_util.jit', 'numba_util.jit', ([], {}), '()\n', (20232, 20234), False, 'from autoarray import numba_util\n'), ((22955, 22971), 'autoarray.numba_util.jit', 'numba_util.jit', ([], {}), '()\n', (22969, 22971), False, 'from autoarray import numba_util\n'), ((25401, 25417), 'autoarray.numba_util.jit', 'numba_util.jit', ([], {}), '()\n', (25415, 25417), False, 'from autoarray import numba_util\n'), ((27988, 28004), 'autoarray.numba_util.jit', 'numba_util.jit', ([], {}), '()\n', (28002, 28004), False, 'from autoarray import numba_util\n'), ((30644, 30660), 'autoarray.numba_util.jit', 'numba_util.jit', ([], {}), '()\n', (30658, 30660), False, 'from autoarray import numba_util\n'), ((34662, 34678), 'autoarray.numba_util.jit', 'numba_util.jit', ([], {}), '()\n', (34676, 34678), False, 'from autoarray import numba_util\n'), ((3015, 3071), 'autoarray.mask.mask_2d_util.total_sub_pixels_2d_from', 'mask_2d_util.total_sub_pixels_2d_from', (['mask_2d', 'sub_size'], {}), '(mask_2d, sub_size)\n', (3052, 3071), False, 'from autoarray.mask import mask_2d_util\n'), ((3091, 3128), 'numpy.zeros', 'np.zeros', ([], {'shape': '(total_sub_pixels, 2)'}), '(shape=(total_sub_pixels, 2))\n', (3099, 3128), True, 'import numpy as np\n'), ((3153, 3274), 'autoarray.geometry.geometry_util.central_scaled_coordinate_2d_from', 'geometry_util.central_scaled_coordinate_2d_from', ([], {'shape_native': 'mask_2d.shape', 'pixel_scales': 'pixel_scales', 'origin': 'origin'}), '(shape_native=mask_2d.shape,\n    pixel_scales=pixel_scales, origin=origin)\n', (3200, 3274), False, 'from autoarray.geometry import geometry_util\n'), ((14632, 14657), 'numpy.zeros', 'np.zeros', (['(shape_slim, 2)'], {}), '((shape_slim, 2))\n', (14640, 14657), True, 'import numpy as np\n'), ((16936, 16979), 'numpy.zeros', 'np.zeros', (['(grid_scaled_2d_slim.shape[0], 2)'], {}), '((grid_scaled_2d_slim.shape[0], 2))\n', (16944, 16979), True, 'import numpy as np\n'), ((17004, 17124), 'autoarray.geometry.geometry_util.central_scaled_coordinate_2d_from', 'geometry_util.central_scaled_coordinate_2d_from', ([], {'shape_native': 'shape_native', 'pixel_scales': 'pixel_scales', 'origin': 'origin'}), '(shape_native=shape_native,\n    pixel_scales=pixel_scales, origin=origin)\n', (17051, 17124), False, 'from autoarray.geometry import geometry_util\n'), ((19548, 19591), 'numpy.zeros', 'np.zeros', (['(grid_scaled_2d_slim.shape[0], 2)'], {}), '((grid_scaled_2d_slim.shape[0], 2))\n', (19556, 19591), True, 'import numpy as np\n'), ((19616, 19736), 'autoarray.geometry.geometry_util.central_scaled_coordinate_2d_from', 'geometry_util.central_scaled_coordinate_2d_from', ([], {'shape_native': 'shape_native', 'pixel_scales': 'pixel_scales', 'origin': 'origin'}), '(shape_native=shape_native,\n    pixel_scales=pixel_scales, origin=origin)\n', (19663, 19736), False, 'from autoarray.geometry import geometry_util\n'), ((22623, 22661), 'numpy.zeros', 'np.zeros', (['grid_pixels_2d_slim.shape[0]'], {}), '(grid_pixels_2d_slim.shape[0])\n', (22631, 22661), True, 'import numpy as np\n'), ((24778, 24821), 'numpy.zeros', 'np.zeros', (['(grid_pixels_2d_slim.shape[0], 2)'], {}), '((grid_pixels_2d_slim.shape[0], 2))\n', (24786, 24821), True, 'import numpy as np\n'), ((24846, 24966), 'autoarray.geometry.geometry_util.central_scaled_coordinate_2d_from', 'geometry_util.central_scaled_coordinate_2d_from', ([], {'shape_native': 'shape_native', 'pixel_scales': 'pixel_scales', 'origin': 'origin'}), '(shape_native=shape_native,\n    pixel_scales=pixel_scales, origin=origin)\n', (24893, 24966), False, 'from autoarray.geometry import geometry_util\n'), ((27341, 27404), 'numpy.zeros', 'np.zeros', (['(grid_scaled_2d.shape[0], grid_scaled_2d.shape[1], 2)'], {}), '((grid_scaled_2d.shape[0], grid_scaled_2d.shape[1], 2))\n', (27349, 27404), True, 'import numpy as np\n'), ((27429, 27549), 'autoarray.geometry.geometry_util.central_scaled_coordinate_2d_from', 'geometry_util.central_scaled_coordinate_2d_from', ([], {'shape_native': 'shape_native', 'pixel_scales': 'pixel_scales', 'origin': 'origin'}), '(shape_native=shape_native,\n    pixel_scales=pixel_scales, origin=origin)\n', (27476, 27549), False, 'from autoarray.geometry import geometry_util\n'), ((29283, 29303), 'numpy.zeros', 'np.zeros', (['grid.shape'], {}), '(grid.shape)\n', (29291, 29303), True, 'import numpy as np\n'), ((29366, 29377), 'numpy.zeros', 'np.zeros', (['(2)'], {}), '(2)\n', (29374, 29377), True, 'import numpy as np\n'), ((29402, 29430), 'numpy.mean', 'np.mean', (['border_grid[:, (0)]'], {}), '(border_grid[:, (0)])\n', (29409, 29430), True, 'import numpy as np\n'), ((29453, 29481), 'numpy.mean', 'np.mean', (['border_grid[:, (1)]'], {}), '(border_grid[:, (1)])\n', (29460, 29481), True, 'import numpy as np\n'), ((29721, 29746), 'numpy.min', 'np.min', (['border_grid_radii'], {}), '(border_grid_radii)\n', (29727, 29746), True, 'import numpy as np\n'), ((32486, 32598), 'autoarray.structures.arrays.two_d.array_2d_util.array_2d_slim_from', 'array_2d_util.array_2d_slim_from', ([], {'array_2d_native': 'grid_2d_native[:, :, (0)]', 'mask_2d': 'mask', 'sub_size': 'sub_size'}), '(array_2d_native=grid_2d_native[:, :, (0)],\n    mask_2d=mask, sub_size=sub_size)\n', (32518, 32598), False, 'from autoarray.structures.arrays.two_d import array_2d_util\n'), ((32633, 32745), 'autoarray.structures.arrays.two_d.array_2d_util.array_2d_slim_from', 'array_2d_util.array_2d_slim_from', ([], {'array_2d_native': 'grid_2d_native[:, :, (1)]', 'mask_2d': 'mask', 'sub_size': 'sub_size'}), '(array_2d_native=grid_2d_native[:, :, (1)],\n    mask_2d=mask, sub_size=sub_size)\n', (32665, 32745), False, 'from autoarray.structures.arrays.two_d import array_2d_util\n'), ((32770, 32821), 'numpy.stack', 'np.stack', (['(grid_1d_slim_y, grid_1d_slim_x)'], {'axis': '(-1)'}), '((grid_1d_slim_y, grid_1d_slim_x), axis=-1)\n', (32778, 32821), True, 'import numpy as np\n'), ((34318, 34428), 'autoarray.structures.arrays.two_d.array_2d_util.array_2d_native_from', 'array_2d_util.array_2d_native_from', ([], {'array_2d_slim': 'grid_2d_slim[:, (0)]', 'mask_2d': 'mask_2d', 'sub_size': 'sub_size'}), '(array_2d_slim=grid_2d_slim[:, (0)],\n    mask_2d=mask_2d, sub_size=sub_size)\n', (34352, 34428), False, 'from autoarray.structures.arrays.two_d import array_2d_util\n'), ((34465, 34575), 'autoarray.structures.arrays.two_d.array_2d_util.array_2d_native_from', 'array_2d_util.array_2d_native_from', ([], {'array_2d_slim': 'grid_2d_slim[:, (1)]', 'mask_2d': 'mask_2d', 'sub_size': 'sub_size'}), '(array_2d_slim=grid_2d_slim[:, (1)],\n    mask_2d=mask_2d, sub_size=sub_size)\n', (34499, 34575), False, 'from autoarray.structures.arrays.two_d import array_2d_util\n'), ((34600, 34655), 'numpy.stack', 'np.stack', (['(grid_2d_native_y, grid_2d_native_x)'], {'axis': '(-1)'}), '((grid_2d_native_y, grid_2d_native_x), axis=-1)\n', (34608, 34655), True, 'import numpy as np\n'), ((35435, 35496), 'numpy.zeros', 'np.zeros', ([], {'shape': '(grid_slim.shape[0] * upscale_factor ** 2, 2)'}), '(shape=(grid_slim.shape[0] * upscale_factor ** 2, 2))\n', (35443, 35496), True, 'import numpy as np\n'), ((36925, 36955), 'numpy.asarray', 'np.asarray', (['y_inside', 'x_inside'], {}), '(y_inside, x_inside)\n', (36935, 36955), True, 'import numpy as np\n'), ((644, 672), 'numpy.max', 'np.max', (['grid_2d_slim[:, (0)]'], {}), '(grid_2d_slim[:, (0)])\n', (650, 672), True, 'import numpy as np\n'), ((673, 701), 'numpy.min', 'np.min', (['grid_2d_slim[:, (0)]'], {}), '(grid_2d_slim[:, (0)])\n', (679, 701), True, 'import numpy as np\n'), ((724, 752), 'numpy.max', 'np.max', (['grid_2d_slim[:, (1)]'], {}), '(grid_2d_slim[:, (1)])\n', (730, 752), True, 'import numpy as np\n'), ((753, 781), 'numpy.min', 'np.min', (['grid_2d_slim[:, (1)]'], {}), '(grid_2d_slim[:, (1)])\n', (759, 781), True, 'import numpy as np\n'), ((8748, 8793), 'numpy.full', 'np.full', ([], {'fill_value': '(False)', 'shape': 'shape_native'}), '(fill_value=False, shape=shape_native)\n', (8755, 8793), True, 'import numpy as np\n'), ((10888, 10933), 'numpy.full', 'np.full', ([], {'fill_value': '(False)', 'shape': 'shape_native'}), '(fill_value=False, shape=shape_native)\n', (10895, 10933), True, 'import numpy as np\n'), ((29554, 29604), 'numpy.subtract', 'np.subtract', (['border_grid[:, (0)]', 'border_origin[0]'], {}), '(border_grid[:, (0)], border_origin[0])\n', (29565, 29604), True, 'import numpy as np\n'), ((29628, 29678), 'numpy.subtract', 'np.subtract', (['border_grid[:, (1)]', 'border_origin[1]'], {}), '(border_grid[:, (1)], border_origin[1])\n', (29639, 29678), True, 'import numpy as np\n'), ((29816, 29859), 'numpy.subtract', 'np.subtract', (['grid[:, (0)]', 'border_origin[0]'], {}), '(grid[:, (0)], border_origin[0])\n', (29827, 29859), True, 'import numpy as np\n'), ((29883, 29926), 'numpy.subtract', 'np.subtract', (['grid[:, (1)]', 'border_origin[1]'], {}), '(grid[:, (1)], border_origin[1])\n', (29894, 29926), True, 'import numpy as np\n'), ((30116, 30169), 'numpy.square', 'np.square', (['(grid[pixel_index, 0] - border_grid[:, (0)])'], {}), '(grid[pixel_index, 0] - border_grid[:, (0)])\n', (30125, 30169), True, 'import numpy as np\n'), ((30187, 30240), 'numpy.square', 'np.square', (['(grid[pixel_index, 1] - border_grid[:, (1)])'], {}), '(grid[pixel_index, 1] - border_grid[:, (1)])\n', (30196, 30240), True, 'import numpy as np\n'), ((37078, 37091), 'numpy.roll', 'np.roll', (['y', '(1)'], {}), '(y, 1)\n', (37085, 37091), True, 'import numpy as np\n'), ((37105, 37118), 'numpy.roll', 'np.roll', (['x', '(1)'], {}), '(x, 1)\n', (37112, 37118), True, 'import numpy as np\n')]