Source code for autoarray.structures.arrays.uniform_2d

from astropy.io import fits
import logging
import numpy as np
from pathlib import Path
from typing import List, Optional, Tuple, Union

from autoconf import conf

from autoarray.mask.mask_2d import Mask2D
from autoarray.structures.abstract_structure import Structure
from autoarray.structures.header import Header
from autoarray.structures.arrays.uniform_1d import Array1D

from autoarray import exc
from autoarray import type as ty

from autoarray.structures.arrays import array_2d_util
from autoarray.geometry import geometry_util
from autoarray.layout import layout_util
from autoarray.numpy_wrapper import numpy as npw


logging.basicConfig()
logger = logging.getLogger(__name__)


class AbstractArray2D(Structure):
    def __init__(
        self,
        values: Union[np.ndarray, List, "AbstractArray2D"],
        mask: Mask2D,
        header: Header = None,
        store_native: bool = False,
        skip_mask: bool = False,
        *args,
        **kwargs,
    ):
        """
        A uniform 2D array of values, which are paired with a 2D mask of pixels which may be split into sub-pixels.

        The ``Array2D`, like all data structures (e.g. ``Grid2D``, ``VectorYX2D``) has in-built functionality which:

        - Applies a 2D mask (a ``Mask2D`` object) to the da_ta structure's values.

        - Maps the data structure between two data representations: `slim`` (all unmasked values in
          a 1D ``ndarray``) and ``native`` (all unmasked values in a 2D ``ndarray``).

        - Associates Cartesian ``Grid2D`` objects of (y,x) coordinates with the data structure (e.g.
          a (y,x) grid of all unmasked pixels).

        - Associates sub-grids with the data structure, which perform calculations higher resolutions which are then
          binned up.

        Each entry of an ``Array2D`` corresponds to a value at the centre of a sub-pixel in its
        corresponding ``Mask2D``.  It is ordered such that pixels begin from the top-row of the corresponding mask
        and go right and down. The positive y-axis is upwards and positive x-axis to the right.

        A detailed description of the data structure API is provided below.

        **SLIM DATA REPRESENTATION (sub-size=1)**

        Below is a visual illustration of an ``Array2D``'s 2D mask, where a total of 10 pixels are unmasked and are
        included in the array.

        ::

             x x x x x x x x x x
             x x x x x x x x x x     This is an example ``Mask2D``, where:
             x x x x x x x x x x
             x x x x O O x x x x     x = `True` (Pixel is masked and excluded from the array)
             x x x O O O O x x x     O = `False` (Pixel is not masked and included in the array)
             x x x O O O O x x x
             x x x x x x x x x x
             x x x x x x x x x x
             x x x x x x x x x x
             x x x x x x x x x x

        The mask pixel index's are as follows (the positive / negative direction of the ``Grid2D`` objects associated
        with the array are also shown on the y and x axes).

        ::

            <--- -ve  x  +ve -->

             x x x x x x x x x x  ^   array_2d[0] = 10
             x x x x x x x x x x  I   array_2d[1] = 20
             x x x x x x x x x x  I   array_2d[2] = 30
             x x x x 0 1 x x x x +ve  array_2d[3] = 40
             x x x 2 3 4 5 x x x  y   array_2d[4] = 50
             x x x 6 7 8 9 x x x -ve  array_2d[5] = 60
             x x x x x x x x x x  I   array_2d[6] = 70
             x x x x x x x x x x  I   array_2d[7] = 80
             x x x x x x x x x x \/   array_2d[8] = 90
             x x x x x x x x x x      array_2d[9] = 100

        The ``Array2D`` in its ``slim`` data representation is an ``ndarray`` of shape [total_unmasked_pixels].

        For the ``Mask2D`` above the ``slim`` representation therefore contains 10 entries and two examples of these
        entries are:

        ::

            array[3] = the 4th unmasked pixel's value, given by value 40 above.
            array[6] = the 7th unmasked pixel's value, given by value 80 above.

        A Cartesian grid of (y,x) coordinates, corresponding to all ``slim`` values (e.g. unmasked pixels) is given
        by ``array_2d.derive_grid.masked.slim``.


        **NATIVE DATA REPRESENTATION (sub_size=1)**

        The ``Array2D`` above, but represented as an an ``ndarray`` of shape [total_y_values, total_x_values], where
        all masked entries have values of 0.0.

        For the following mask:

        ::

             x x x x x x x x x x
             x x x x x x x x x x     This is an example ``Mask2D``, where:
             x x x x x x x x x x
             x x x x O O x x x x     x = `True` (Pixel is masked and excluded from the array)
             x x x O O O O x x x     O = `False` (Pixel is not masked and included in the array)
             x x x O O O O x x x
             x x x x x x x x x x
             x x x x x x x x x x
             x x x x x x x x x x
             x x x x x x x x x x

        Where the array has the following indexes (left figure) and values (right):

        ::

            <--- -ve  x  +ve -->

             x x x x x x x x x x  ^   array_2d[0] = 10
             x x x x x x x x x x  I   array_2d[1] = 20
             x x x x x x x x x x  I   array_2d[2] = 30
             x x x x 0 1 x x x x +ve  array_2d[3] = 40
             x x x 2 3 4 5 x x x  y   array_2d[4] = 50
             x x x 6 7 8 9 x x x -ve  array_2d[5] = 60
             x x x x x x x x x x  I   array_2d[6] = 70
             x x x x x x x x x x  I   array_2d[7] = 80
             x x x x x x x x x x \/   array_2d[8] = 90
             x x x x x x x x x x      array_2d[9] = 100

        In the above array:

        ::

            - array[0,0] = 0.0 (it is masked, thus zero)
            - array[0,0] = 0.0 (it is masked, thus zero)
            - array[3,3] = 0.0 (it is masked, thus zero)
            - array[3,3] = 0.0 (it is masked, thus zero)
            - array[3,4] = 10
            - array[3,5] = 20
            - array[4,5] = 50

        **SLIM TO NATIVE MAPPING**

        The ``Array2D`` has functionality which maps data between the ``slim`` and ``native`` data representations.

        For the example mask above, the 1D ``ndarray`` given by ``mask.derive_indexes.slim_to_native`` is:

        ::

            slim_to_native[0] = [3,4]
            slim_to_native[1] = [3,5]
            slim_to_native[2] = [4,3]
            slim_to_native[3] = [4,4]
            slim_to_native[4] = [4,5]
            slim_to_native[5] = [4,6]
            slim_to_native[6] = [5,3]
            slim_to_native[7] = [5,4]
            slim_to_native[8] = [5,5]
            slim_to_native[9] = [5,6]

        **SUB GRIDDING**

        If the ``Mask2D`` ``sub_size`` is > 1, the array has entries corresponding to the values at the centre of
        every sub-pixel of each unmasked pixel.

        The sub-array indexes are ordered such that pixels begin from the first (top-left) sub-pixel in the first
        unmasked pixel. Indexes then go over the sub-pixels in each unmasked pixel, for every unmasked pixel.

        Therefore, the shapes of the sub-array are as follows:

        - ``slim`` representation: an ``ndarray`` of shape [total_unmasked_pixels*sub_size**2].
        - ``native`` representation: an ``ndarray`` of shape [total_y_values*sub_size, total_x_values*sub_size].

        Below is a visual illustration of a sub array. Indexing of each sub-pixel goes from the top-left corner. In
        contrast to the array above, our illustration below restricts the mask to just 2 pixels, to keep the
        illustration brief.

        ::

             x x x x x x x x x x
             x x x x x x x x x x     This is an example ``Mask2D``, where:
             x x x x x x x x x x
             x x x x x x x x x x     x = `True` (Pixel is masked and excluded from lens)
             x 0 0 x x x x x x x     O = `False` (Pixel is not masked and included in lens)
             x x x x x x x x x x
             x x x x x x x x x x
             x x x x x x x x x x
             x x x x x x x x x x
             x x x x x x x x x x

        If ``sub_size=2``, each unmasked pixel has 4 (2x2) sub-pixel values. For the example above, pixels 0 and 1
        each have 4 values which map to the ``array_2d``'s ``slim`` representation as follows:

        ::

            Pixel 0 - (2x2):

                   array_2d.slim[0] = value of first sub-pixel in pixel 0.
             0 1   array_2d.slim[1] = value of first sub-pixel in pixel 1.
             2 3   array_2d.slim[2] = value of first sub-pixel in pixel 2.
                   array_2d.slim[3] = value of first sub-pixel in pixel 3.

            Pixel 1 - (2x2):

                   array_2d.slim[4] = value of first sub-pixel in pixel 0.
             4 5   array_2d.slim[5] = value of first sub-pixel in pixel 1.
             6 7   array_2d.slim[6] = value of first sub-pixel in pixel 2.
                   array_2d.slim[7] = value of first sub-pixel in pixel 3.

        For the ``native`` data representation we get the following mappings:

        ::

            Pixel 0 - (2x2):

                   array_2d.native[8, 2] = value of first sub-pixel in pixel 0.
             0 1   array_2d.native[8, 3] = value of first sub-pixel in pixel 1.
             2 3   array_2d.native[9, 2] = value of first sub-pixel in pixel 2.
                   array_2d.native[9, 3] = value of first sub-pixel in pixel 3.

            Pixel 1 - (2x2):

                   array_2d.native[10, 4] = value of first sub-pixel in pixel 0.
             4 5   array_2d.native[10, 5] = value of first sub-pixel in pixel 1.
             6 7   array_2d.native[11, 4] = value of first sub-pixel in pixel 2.
                   array_2d.native[11, 5] = value of first sub-pixel in pixel 3.

            Other entries (all masked sub-pixels are zero):

                   array_2d.native[0, 0] = 0.0 (it is masked, thus zero)
                   array_2d.native[15, 12] = 0.0 (it is masked, thus zero)

        If we used a sub_size of 3, for pixel 0 we we would create a 3x3 sub-array:

        ::

                     array_2d.slim[0] = value of first sub-pixel in pixel 0.
                     array_2d.slim[1] = value of first sub-pixel in pixel 1.
                     array_2d.slim[2] = value of first sub-pixel in pixel 2.
             0 1 2   array_2d.slim[3] = value of first sub-pixel in pixel 3.
             3 4 5   array_2d.slim[4] = value of first sub-pixel in pixel 4.
             6 7 8   array_2d.slim[5] = value of first sub-pixel in pixel 5.
                     array_2d.slim[6] = value of first sub-pixel in pixel 6.
                     array_2d.slim[7] = value of first sub-pixel in pixel 7.
                     array_2d.slim[8] = value of first sub-pixel in pixel 8.

        In **PyAutoCTI** all `Array2D` objects are used in their `native` representation without sub-gridding.
        Significant memory can be saved by only store this format, thus the `native_binned_only` config override
        can force this behaviour. It is recommended users do not use this option to avoid unexpected behaviour.

        Parameters
        ----------
        values
            The values of the array, which can be input in the ``slim`` or ``native`` format.
        mask
            The 2D mask associated with the array, defining the pixels each array value in its ``slim`` representation
            is paired with.
        store_native
            If True, the ndarray is stored in its native format [total_y_pixels, total_x_pixels]. This avoids
            mapping large data arrays to and from the slim / native formats, which can be a computational bottleneck.

        Examples
        --------

        This example uses the ``Array2D.no_mask`` method to create the ``Array2D``.

        Different methods using different inputs are available and documented throughout this webpage.

        .. code-block:: python

            import autoarray as aa

            # Make Array2D from input np.ndarray with sub_size 1.

            array_2d = aa.Array2D.no_mask(
                values=np.array([1.0, 2.0, 3.0, 4.0]),
                shape_native=(2, 2),
                pixel_scales=1.0,
                sub_size=1
            )

            # Make Array2D from input list with different shape_native and sub_size 1.

            array_2d = aa.Array2D.no_mask(
                values=[1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
                shape_native=(2, 3),
                pixel_scales=1.0,
                sub_size=1
            )

        .. code-block:: python

            import autoarray as aa

            # Make Array2D with sub_size 2.

            array_2d = aa.Array2D.no_mask(
                values=[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0],
                shape_native=(2, 1),
                pixel_scales=1.0,
                sub_size=2,
            )

            # Apply 2D mask to Array2D with sub_size 2, where the
            # True value masks entries (5.0, 6.0, 7.0, 8.0).

            mask = aa.Mask2D(
                mask=[[False], [True]],
                pixel_scales=2.0,
                sub_size=2
            )

            array_2d = array_2d.apply_mask(mask=mask)

            # Print certain array attributes.

            print(array_2d.slim) # masked 1D data representation on sub-grid.
            print(array_2d.native) # masked 2D data representation on sub-grid.
            print(array_2d.slim.binned) # masked 1D data representation binned up from sub-grid.
            print(array_2d.native.binned) # masked 2D data representation binned up from sub-grid.

            # Output array to .fits file.

            array_2d.output_to_fits(file_path="/path/for/output")
        """

        try:
            values = values._array
        except AttributeError:
            pass
        if conf.instance["general"]["structures"]["native_binned_only"]:
            store_native = True

        values = array_2d_util.convert_array_2d(
            array_2d=values,
            mask_2d=mask,
            store_native=store_native,
            skip_mask=skip_mask,
        )

        super().__init__(values)
        self.mask = mask
        self.header = header

    @property
    def values(self):
        return self._array

    def __array_finalize__(self, obj):
        if hasattr(obj, "mask"):
            self.mask = obj.mask

        if hasattr(obj, "header"):
            self.header = obj.header
        else:
            self.header = None

    @property
    def store_native(self):
        return len(self.shape) != 1

    def apply_mask(self, mask: Mask2D) -> "Array2D":
        return Array2D(values=self.native, mask=mask, header=self.header)

    @property
    def slim(self) -> "Array2D":
        """
        Return an `Array2D` where the data is stored its `slim` representation, which is an ``ndarray`` of shape
        [total_unmasked_pixels * sub_size**2].

        If it is already stored in its `slim` representation it is returned as it is. If not, it is mapped from
        `native` to `slim` and returned as a new `Array2D`.
        """
        return Array2D(values=self, mask=self.mask, header=self.header)

    @property
    def native(self) -> "Array2D":
        """
        Return a `Array2D` where the data is stored in its `native` representation, which is an ``ndarray`` of shape
        [sub_size*total_y_pixels, sub_size*total_x_pixels].

        If it is already stored in its `native` representation it is return as it is. If not, it is mapped from
        `slim` to `native` and returned as a new `Array2D`.
        """
        return Array2D(
            values=self, mask=self.mask, header=self.header, store_native=True
        )

    @property
    def native_skip_mask(self) -> "Array2D":
        """
        Return a `Array2D` where the data is stored in its `native` representation, which is an ``ndarray`` of shape
        [sub_size*total_y_pixels, sub_size*total_x_pixels].

        If it is already stored in its `native` representation it is return as it is. If not, it is mapped from
        `slim` to `native` and returned as a new `Array2D`.
        """
        return Array2D(
            values=self,
            mask=self.mask,
            header=self.header,
            store_native=True,
            skip_mask=True,
        )

    @property
    def binned(self) -> "Array2D":
        """
        Convenience method to access the binned-up array in its 1D representation, which is a Grid2D stored as an
        ``ndarray`` of shape [total_unmasked_pixels, 2].

        The binning up process converts a array from (y,x) values where each value is a coordinate on the sub-array to
        (y,x) values where each coordinate is at the centre of its mask (e.g. a array with a sub_size of 1). This is
        performed by taking the mean of all (y,x) values in each sub pixel.

        If the array is stored in 1D it is return as is. If it is stored in 2D, it must first be mapped from 2D to 1D.

        In **PyAutoCTI** all `Array2D` objects are used in their `native` representation without sub-gridding.
        Significant memory can be saved by only store this format, thus the `native_binned_only` config override
        can force this behaviour. It is recommended users do not use this option to avoid unexpected behaviour.
        """
        if conf.instance["general"]["structures"]["native_binned_only"]:
            return self

        array_2d_slim = self.slim

        binned_array_1d = npw.multiply(
            self.mask.sub_fraction,
            array_2d_slim.reshape(-1, self.mask.sub_length).sum(axis=1),
        )

        return Array2D(
            values=binned_array_1d,
            mask=self.mask.derive_mask.sub_1,
            header=self.header,
        )

    @property
    def in_counts(self) -> "Array2D":
        return self.header.array_eps_to_counts(array_eps=self)

    @property
    def in_counts_per_second(self) -> "Array2D":
        return self.header.array_counts_to_counts_per_second(
            array_counts=self.in_counts
        )

    @property
    def original_orientation(self) -> Union[np.ndarray, "Array2D"]:
        return layout_util.rotate_array_via_roe_corner_from(
            array=np.array(self), roe_corner=self.header.original_roe_corner
        )

    @property
    def readout_offsets(self) -> Tuple[int, int]:
        if self.header is not None:
            if self.header.readout_offsets is not None:
                return self.header.readout_offsets
        return (0, 0)

    @property
    def binned_across_rows(self) -> Array1D:
        """
        Bins the 2D array up to a 1D array, where each value is the mean of all unmasked values in each row.
        """
        binned_array = np.mean(self.native.array, axis=0, where=~self.mask)

        # binned_array = (self.native * np.invert(self.mask)).sum(axis=0) / np.invert(
        #     self.mask
        # ).sum(axis=0)
        return Array1D.no_mask(values=binned_array, pixel_scales=self.pixel_scale)

    @property
    def binned_across_columns(self) -> Array1D:
        """
        Bins the 2D array up to a 1D array, where each value is the mean of all unmasked values in each column.
        """
        binned_array = np.mean(self.native.array, axis=1, where=~self.mask)

        # binned_array = (self.native*np.invert(self.mask)).sum(axis=1)/np.invert(self.mask).sum(axis=1)
        return Array1D.no_mask(values=binned_array, pixel_scales=self.pixel_scale)

    def zoomed_around_mask(self, buffer: int = 1) -> "Array2D":
        """
        Extract the 2D region of an array corresponding to the rectangle encompassing all unmasked values.

        This is used to extract and visualize only the region of an image that is used in an analysis.

        Parameters
        ----------
        buffer
            The number pixels around the extracted array used as a buffer.
        """

        extracted_array_2d = array_2d_util.extracted_array_2d_from(
            array_2d=np.array(self.native),
            y0=self.mask.zoom_region[0] - buffer,
            y1=self.mask.zoom_region[1] + buffer,
            x0=self.mask.zoom_region[2] - buffer,
            x1=self.mask.zoom_region[3] + buffer,
        )

        mask = Mask2D.all_false(
            shape_native=extracted_array_2d.shape,
            pixel_scales=self.pixel_scales,
            sub_size=self.sub_size,
            origin=self.mask.mask_centre,
        )

        array = array_2d_util.convert_array_2d(
            array_2d=extracted_array_2d, mask_2d=mask
        )

        return Array2D(values=array, mask=mask, header=self.header)

    def extent_of_zoomed_array(self, buffer: int = 1) -> np.ndarray:
        """
        For an extracted zoomed array computed from the method *zoomed_around_mask* compute its extent in scaled
        coordinates.

        The extent of the grid in scaled units returned as an ``ndarray`` of the form [x_min, x_max, y_min, y_max].

        This is used visualize zoomed and extracted arrays via the imshow() method.

        Parameters
        ----------
        buffer
            The number pixels around the extracted array used as a buffer.
        """
        extracted_array_2d = array_2d_util.extracted_array_2d_from(
            array_2d=np.array(self.native),
            y0=self.mask.zoom_region[0] - buffer,
            y1=self.mask.zoom_region[1] + buffer,
            x0=self.mask.zoom_region[2] - buffer,
            x1=self.mask.zoom_region[3] + buffer,
        )

        mask = Mask2D.all_false(
            shape_native=extracted_array_2d.shape,
            pixel_scales=self.pixel_scales,
            sub_size=self.sub_size,
            origin=self.mask.mask_centre,
        )

        return mask.geometry.extent

    def resized_from(
        self, new_shape: Tuple[int, int], mask_pad_value: int = 0.0
    ) -> "Array2D":
        """
        Resize the array around its centre to a new input shape.

        If a new_shape dimension is smaller than the current dimension, the data at the edges is trimmed and removed.
        If it is larger, the data is padded with zeros.

        If the array has even sized dimensions, the central pixel around which data is trimmed / padded is chosen as
        the top-left pixel of the central quadrant of pixels.

        Parameters
        ----------
        new_shape
            The new 2D shape of the array.
        """

        resized_array_2d = array_2d_util.resized_array_2d_from(
            array_2d=np.array(self.native), resized_shape=new_shape
        )

        resized_mask = self.mask.derive_mask.resized_from(
            new_shape=new_shape, pad_value=mask_pad_value
        )

        array = array_2d_util.convert_array_2d(
            array_2d=resized_array_2d, mask_2d=resized_mask
        )

        return Array2D(
            values=array,
            mask=resized_mask,
            header=self.header,
            store_native=self.store_native,
        )

    def padded_before_convolution_from(
        self, kernel_shape: Tuple[int, int], mask_pad_value: int = 0.0
    ) -> "Array2D":
        """
        When the edge pixels of a mask are unmasked and a convolution is to occur, the signal of edge pixels will be
        'missing' if the grid is used to evaluate the signal via an analytic function.

        To ensure this signal is included the array can be padded, where it is 'buffed' such that it includes all
        pixels whose signal will be convolved into the unmasked pixels given the 2D kernel shape. The values of
        these pixels are zeros.

        Parameters
        ----------
        kernel_shape
            The 2D shape of the kernel which convolves signal from masked pixels to unmasked pixels.
        """
        new_shape = (
            self.shape_native[0] + (kernel_shape[0] - 1),
            self.shape_native[1] + (kernel_shape[1] - 1),
        )
        return self.resized_from(new_shape=new_shape, mask_pad_value=mask_pad_value)

    def trimmed_after_convolution_from(
        self, kernel_shape: Tuple[int, int]
    ) -> "Array2D":
        """
        When the edge pixels of a mask are unmasked and a convolution is to occur, the signal of edge pixels will be
        'missing' if the grid is used to evaluate the signal via an analytic function.

        To ensure this signal is included the array can be padded, a padded array can be computed via the method
        *padded_before_convolution_from*. This function trims the array back to its original shape, after the padded array
        has been used for computational.

        Parameters
        ----------
        kernel_shape
            The 2D shape of the kernel which convolves signal from masked pixels to unmasked pixels.
        """
        psf_cut_y = int(np.ceil(kernel_shape[0] / 2)) - 1
        psf_cut_x = int(np.ceil(kernel_shape[1] / 2)) - 1
        array_y = int(self.mask.shape[0])
        array_x = int(self.mask.shape[1])
        trimmed_array_2d = self.native[
            psf_cut_y : array_y - psf_cut_y, psf_cut_x : array_x - psf_cut_x
        ]

        resized_mask = self.mask.derive_mask.resized_from(
            new_shape=trimmed_array_2d.shape
        )

        array = array_2d_util.convert_array_2d(
            array_2d=trimmed_array_2d, mask_2d=resized_mask
        )

        return Array2D(
            values=array,
            mask=resized_mask,
            header=self.header,
            store_native=self.store_native,
        )

    @property
    def hdu_for_output(self) -> fits.PrimaryHDU:
        """
        The array as an HDU object, which can be output to a .fits file.

        The header of the HDU is used to store the `pixel_scale` of the array, which is used by the `Array2D.from_hdu`.

        This method is used in other projects (E.g. PyAutoGalaxy, PyAutoLens) to conveniently output the array to .fits
        files.

        Returns
        -------
        The HDU containing the data and its header which can then be written to .fits.
        """
        return array_2d_util.hdu_for_output_from(
            array_2d=np.array(self.native), header_dict=self.pixel_scale_header
        )

    def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False):
        """
        Output the array to a .fits file.

        The `pixel_scale` is stored in the header as `PIXSCALE`, which is used by the `Array2D.from_primary_hdu`
        method.

        Parameters
        ----------
        file_path
            The output path of the file, including the filename and the `.fits` extension e.g. '/path/to/filename.fits'
        overwrite
            If a file already exists at the path, if overwrite=True it is overwritten else an error is raised.
        """
        array_2d_util.numpy_array_2d_to_fits(
            array_2d=np.array(self.native),
            file_path=file_path,
            overwrite=overwrite,
            header_dict=self.pixel_scale_header,
        )


[docs]class Array2D(AbstractArray2D):
[docs] @classmethod def no_mask( cls, values: Union[np.ndarray, List, AbstractArray2D], pixel_scales: ty.PixelScales, shape_native: Tuple[int, int] = None, sub_size: int = 1, origin: Tuple[float, float] = (0.0, 0.0), header: Optional[Header] = None, ) -> "Array2D": """ Returns an ``Array2D`` from an array via inputs in its slim or native data representation. From a ``slim`` 1D input the method cannot determine the 2D shape of the array and its mask. The ``shape_native`` must therefore also be input into this method. The mask is setup as a unmasked `Mask2D` of ``shape_native``. For a full description of ``Array2D`` objects, including a description of the ``slim`` and ``native`` attribute used by the API, see the :meth:`Array2D class API documentation <autoarray.structures.arrays.uniform_2d.AbstractArray2D.__new__>`. Parameters ---------- values The values of the array input with shape [total_unmasked_pixels*(sub_size**2)] or shape [total_y_pixels*sub_size, total_x_pixel*sub_size]. pixel_scales The (y,x) scaled units to pixel units conversion factors of every pixel. If this is input as a `float`, it is converted to a (float, float) structure. shape_native The 2D shape of the array in its ``native`` format, and its 2D mask (only required if input shape is in ``slim`` format). sub_size The size (sub_size x sub_size) of each unmasked pixels sub-array. origin The (y,x) scaled units origin of the mask's coordinate system. Examples -------- .. code-block:: python import autoarray as aa # Make Array2D from input list, native format with sub_size 1 # (This array has shape_native=(2,2)). array_2d = aa.Array2D.manual( array=np.array([[1.0, 2.0], [3.0, 4.0]]), pixel_scales=1.0. sub_size=1 ) .. code-block:: python import autoarray as aa # Make Array2D from input list, slim format with sub_size 2. array_2d = aa.Array2D.no_mask( values=[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], shape_native=(2, 1), pixel_scales=1.0, sub_size=2, ) """ pixel_scales = geometry_util.convert_pixel_scales_2d(pixel_scales=pixel_scales) values = array_2d_util.convert_array(array=values) if len(values.shape) == 1: if shape_native is None: raise exc.ArrayException( f""" The input array is not in its native shape (an ndarray / list of shape [total_y_pixels, total_x_pixels]) and the shape_native parameter has not been input the Array2D function. Either change the input array to be its native shape or input its shape_native input the function. The shape of the input array is {values.shape} """ ) if shape_native and len(shape_native) != 2: raise exc.ArrayException( """ The input shape_native parameter is not a tuple of type (int, int) """ ) else: shape_native = ( int(values.shape[0] / sub_size), int(values.shape[1] / sub_size), ) mask = Mask2D.all_false( shape_native=shape_native, pixel_scales=pixel_scales, sub_size=sub_size, origin=origin, ) return Array2D(values=values, mask=mask, header=header)
[docs] @classmethod def full( cls, fill_value: float, shape_native: Tuple[int, int], pixel_scales: ty.PixelScales, sub_size: int = 1, origin: Tuple[float, float] = (0.0, 0.0), header: Optional[Header] = None, ) -> "Array2D": """ Returns an ``Array2D`` where all values are filled with an input fill value, analogous to ``np.full()``. For a full description of ``Array2D`` objects, including a description of the ``slim`` and ``native`` attribute used by the API, see the :meth:`Array2D class API documentation <autoarray.structures.arrays.uniform_2d.AbstractArray2D.__new__>`. From this input the method cannot determine the 2D shape of the array and its mask. The ``shape_native`` must therefore also be input into this method. The mask is setup as a unmasked `Mask2D` of ``shape_native``. Parameters ---------- fill_value The value all array elements are filled with. shape_native The 2D shape of the array in its ``native`` format, and its 2D mask. pixel_scales The (y,x) scaled units to pixel units conversion factors of every pixel. If this is input as a `float`, it is converted to a (float, float) structure. sub_size The size (sub_size x sub_size) of each unmasked pixels sub-array. origin The (y,x) scaled units origin of the mask's coordinate system. Examples -------- .. code-block:: python import autoarray as aa # Make Array2D with sub_size 1. array_2d = aa.Array2D.full( fill_value=2.0, shape_native=(2, 2), pixel_scales=1.0, sub_size=1 ) .. code-block:: python import autoarray as aa # Make Array2D with sub_size 2. array_2d = aa.Array2D.full( fill_value=2.0, shape_native=(2, 2), pixel_scales=1.0, sub_size=2 ) """ if sub_size is not None: shape_native = (shape_native[0] * sub_size, shape_native[1] * sub_size) return cls.no_mask( values=np.full(fill_value=fill_value, shape=shape_native), pixel_scales=pixel_scales, sub_size=sub_size, origin=origin, header=header, )
[docs] @classmethod def ones( cls, shape_native: Tuple[int, int], pixel_scales: ty.PixelScales, sub_size: int = 1, origin: Tuple[float, float] = (0.0, 0.0), header: Header = None, ) -> "Array2D": """ Returns an ``Array2D`` where all values are filled with ones, analogous to ``np.ones()``. For a full description of ``Array2D`` objects, including a description of the ``slim`` and ``native`` attribute used by the API, see the :meth:`Array2D class API documentation <autoarray.structures.arrays.uniform_2d.AbstractArray2D.__new__>`. From this input the method cannot determine the 2D shape of the array and its mask. The ``shape_native`` must therefore also be input into this method. The mask is setup as a unmasked `Mask2D` of ``shape_native``. Parameters ---------- shape_native The 2D shape of the array in its ``native`` format, and its 2D mask. pixel_scales The (y,x) scaled units to pixel units conversion factors of every pixel. If this is input as a `float`, it is converted to a (float, float) structure. sub_size The size (sub_size x sub_size) of each unmasked pixels sub-array. origin The (y,x) scaled units origin of the mask's coordinate system. Examples -------- .. code-block:: python import autoarray as aa # Make Array2D with sub_size 1. array_2d = aa.Array2D.ones( shape_native=(2, 2), pixel_scales=1.0, sub_size=1 ) .. code-block:: python import autoarray as aa # Make Array2D with sub_size 2. array_2d = aa.Array2D.ones( shape_native=(2, 2), pixel_scales=1.0, sub_size=2 ) """ return cls.full( fill_value=1.0, shape_native=shape_native, pixel_scales=pixel_scales, sub_size=sub_size, origin=origin, header=header, )
[docs] @classmethod def zeros( cls, shape_native: Tuple[int, int], pixel_scales: ty.PixelScales, sub_size: int = 1, origin: Tuple[float, float] = (0.0, 0.0), header: Header = None, ) -> "Array2D": """ Returns an ``Array2D`` where all values are filled with zeros, analogous to ``np.zeros()``. For a full description of ``Array2D`` objects, including a description of the ``slim`` and ``native`` attribute used by the API, see the :meth:`Array2D class API documentation <autoarray.structures.arrays.uniform_2d.AbstractArray2D.__new__>`. From this input the method cannot determine the 2D shape of the array and its mask. The ``shape_native`` must therefore also be input into this method. The mask is setup as a unmasked `Mask2D` of ``shape_native``. Parameters ---------- shape_native The 2D shape of the array in its ``native`` format, and its 2D mask. pixel_scales The (y,x) scaled units to pixel units conversion factors of every pixel. If this is input as a `float`, it is converted to a (float, float) structure. sub_size The size (sub_size x sub_size) of each unmasked pixels sub-array. origin The (y,x) scaled units origin of the mask's coordinate system. Examples -------- .. code-block:: python import autoarray as aa # Make Array2D with sub_size 1. array_2d = aa.Array2D.zeros( shape_native=(2, 2), pixel_scales=1.0, sub_size=1 ) .. code-block:: python import autoarray as aa # Make Array2D with sub_size 2. array_2d = aa.Array2D.zeros( shape_native=(2, 2), pixel_scales=1.0, sub_size=2 ) """ return cls.full( fill_value=0.0, shape_native=shape_native, pixel_scales=pixel_scales, sub_size=sub_size, origin=origin, header=header, )
[docs] @classmethod def from_fits( cls, file_path: Union[Path, str], pixel_scales: Optional[ty.PixelScales], hdu: int = 0, sub_size: int = 1, origin: Tuple[float, float] = (0.0, 0.0), ) -> "Array2D": """ Returns an ``Array2D`` by loading the array values from a .fits file. For a full description of ``Array2D`` objects, including a description of the ``slim`` and ``native`` attribute used by the API, see the :meth:`Array2D class API documentation <autoarray.structures.arrays.uniform_2d.AbstractArray2D.__new__>`. Parameters ---------- file_path The path the file is loaded from, including the filename and the `.fits` extension, e.g. '/path/to/filename.fits' pixel_scales The (y,x) scaled units to pixel units conversion factors of every pixel. If this is input as a `float`, it is converted to a (float, float) structure. hdu The Header-Data Unit of the .fits file the array data is loaded from. sub_size The size (sub_size x sub_size) of each unmasked pixels sub-array. origin The (y,x) scaled units origin of the coordinate system. Examples -------- .. code-block:: python import autoarray as aa # Make Array2D with sub_size 1. array_2d = aa.Array2D.from_fits( file_path="path/to/file.fits", hdu=0, pixel_scales=1.0, sub_size=1 ) .. code-block:: python import autoarray as aa # Make Array2D with sub_size 2. # (It is uncommon that a sub-gridded array would be loaded from # a .fits, but the API support its). array_2d = aa.Array2D.from_fits( file_path="path/to/file.fits", hdu=0, pixel_scales=1.0, sub_size=2 ) """ array_2d = array_2d_util.numpy_array_2d_via_fits_from( file_path=file_path, hdu=hdu ) header_sci_obj = array_2d_util.header_obj_from(file_path=file_path, hdu=0) header_hdu_obj = array_2d_util.header_obj_from(file_path=file_path, hdu=hdu) return cls.no_mask( values=array_2d, pixel_scales=pixel_scales, sub_size=sub_size, origin=origin, header=Header(header_sci_obj=header_sci_obj, header_hdu_obj=header_hdu_obj), )
[docs] @classmethod def from_primary_hdu( cls, primary_hdu: fits.PrimaryHDU, sub_size: int = 1, origin: Tuple[float, float] = (0.0, 0.0), ) -> "Array2D": """ Returns an ``Array2D`` by from a `PrimaryHDU` object which has been loaded via `astropy.fits` This assumes that the `header` of the `PrimaryHDU` contains an entry named `PIXSCALE` which gives the pixel-scale of the array. For a full description of ``Array2D`` objects, including a description of the ``slim`` and ``native`` attribute used by the API, see the :meth:`Array2D class API documentation <autoarray.structures.arrays.uniform_2d.AbstractArray2D.__new__>`. Parameters ---------- primary_hdu The `PrimaryHDU` object which has already been loaded from a .fits file via `astropy.fits` and contains the array data and the pixel-scale in the header with an entry named `PIXSCALE`. sub_size The size (sub_size x sub_size) of each unmasked pixels sub-array. origin The (y,x) scaled units origin of the coordinate system. Examples -------- .. code-block:: python from astropy.io import fits import autoarray as aa # Make Array2D with sub_size 1. primary_hdu = fits.open("path/to/file.fits") array_2d = aa.Array2D.from_primary_hdu( primary_hdu=primary_hdu, sub_size=1 ) .. code-block:: python import autoarray as aa # Make Array2D with sub_size 2. # (It is uncommon that a sub-gridded array would be loaded from # a .fits, but the API support its). primary_hdu = fits.open("path/to/file.fits") array_2d = aa.Array2D.from_primary_hdu( primary_hdu=primary_hdu, sub_size=2 ) """ return cls.no_mask( values=cls.flip_hdu_for_ds9(primary_hdu.data.astype("float")), pixel_scales=primary_hdu.header["PIXSCALE"], sub_size=sub_size, origin=origin, header=Header(header_sci_obj=primary_hdu.header), )
[docs] @classmethod def from_yx_and_values( cls, y: Union[np.ndarray, List], x: Union[np.ndarray, List], values: Union[np.ndarray, List], shape_native: Tuple[int, int], pixel_scales: ty.PixelScales, sub_size: int = 1, header: Header = None, ) -> "Array2D": """ Returns an ``Array2D`` by inputting the y and x pixel values where the array is filled and the values that fill it. For a full description of ``Array2D`` objects, including a description of the ``slim`` and ``native`` attribute used by the API, see the :meth:`Array2D class API documentation <autoarray.structures.arrays.uniform_2d.AbstractArray2D.__new__>`. Parameters ---------- y The y pixel indexes where value are input, with shape [total_unmasked_pixels*sub_size]. x The x pixel indexes where value are input, with shape [total_unmasked_pixels*sub_size]. values or list The values which are used to fill in the array, with shape [total_unmasked_pixels*sub_size]. shape_native The 2D shape of the array in its ``native`` format, and its 2D mask. pixel_scales The (y,x) scaled units to pixel units conversion factors of every pixel. If this is input as a `float`, it is converted to a (float, float) structure. sub_size The size (sub_size x sub_size) of each unmasked pixels sub-grid. origin The origin of the grid's mask. Examples -------- .. code-block:: python import autoarray as aa # Make Array2D with sub_size 1. array_2d = aa.Array2D.from_yx_and_values( y=np.array([0.5, 0.5, -0.5, -0.5]), x=np.array([-0.5, 0.5, -0.5, 0.5]), values=np.array([1.0, 2.0, 3.0, 4.0]), shape_native=(2, 2), pixel_scales=1.0, sub_size=1, ) .. code-block:: python import autoarray as aa # Make Array2D with sub_size 2. array_2d = aa.Array2D.from_yx_and_values( y=np.array([1.0, 1.0. 0.5, 0.5, -0.5, -0.5, -1.0, -1.0]), x=np.array([-0.5, 0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5]), values=np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]), shape_native=(2, 1), pixel_scales=1.0, sub_size=2, ) """ pixel_scales = geometry_util.convert_pixel_scales_2d(pixel_scales=pixel_scales) from autoarray.structures.grids.uniform_2d import Grid2D grid = Grid2D.from_yx_1d( y=y, x=x, shape_native=shape_native, pixel_scales=pixel_scales, sub_size=1 ) grid_pixels = geometry_util.grid_pixel_indexes_2d_slim_from( grid_scaled_2d_slim=np.array(grid.slim), shape_native=shape_native, pixel_scales=pixel_scales, ) array_1d = np.array( [values[int(grid_pixels[i])] for i in range(grid_pixels.shape[0])] ) return cls.no_mask( values=array_1d, pixel_scales=pixel_scales, shape_native=shape_native, sub_size=sub_size, header=header, )