diff --git a/autoarray/mask/abstract_mask.py b/autoarray/mask/abstract_mask.py index 02d032cc8..95b4ffa7a 100644 --- a/autoarray/mask/abstract_mask.py +++ b/autoarray/mask/abstract_mask.py @@ -49,6 +49,9 @@ def __init__( converted to a (float, float) structure. origin The origin of the mask's coordinate system in scaled units. + xp + The array module to use (default `numpy`; pass `jax.numpy` for JAX support). Controls + whether internal index arrays are computed on CPU or GPU. """ # noinspection PyArgumentList @@ -62,6 +65,10 @@ def __init__( @property def _xp(self): + """ + Returns the array module in use (`numpy` or `jax.numpy`), determined by whether the mask was + constructed with `xp=jnp`. + """ if self.use_jax: import jax.numpy as jnp @@ -70,6 +77,10 @@ def _xp(self): @property def mask(self): + """ + The boolean ndarray of the mask, where `False` entries are unmasked (used in calculations) + and `True` entries are masked (excluded from calculations). + """ return self._array def __array_finalize__(self, obj): @@ -82,8 +93,10 @@ def __array_finalize__(self, obj): @property def pixel_scale(self) -> float: """ - For a mask with dimensions two or above check that are pixel scales are the same, and if so return this - single value as a float. + The pixel scale of the mask as a single float value. + + For masks with two or more dimensions this assumes all pixel scales are equal and returns + the first entry of ``pixel_scales``. """ return self.pixel_scales[0] @@ -102,6 +115,9 @@ def header_dict(self) -> Dict: @property def dimensions(self) -> int: + """ + The number of dimensions of the mask (e.g. 1 for a 1D mask, 2 for a 2D mask). + """ return len(self.shape) def output_to_fits(self, file_path, overwrite=False): @@ -154,7 +170,7 @@ def is_all_true(self) -> bool: @property def is_all_false(self) -> bool: """ - Returns `False` if all pixels in a mask are `False`, else returns `True`. + Returns `True` if all pixels in a mask are `False` (i.e. every pixel is unmasked), else returns `False`. """ return self.pixels_in_mask == np.size(self._array) diff --git a/autoarray/mask/derive/grid_1d.py b/autoarray/mask/derive/grid_1d.py index b877be139..df0a65ac1 100644 --- a/autoarray/mask/derive/grid_1d.py +++ b/autoarray/mask/derive/grid_1d.py @@ -23,18 +23,18 @@ def __init__(self, mask: Mask1D): omitted (for a full description see the :meth:`Mask1D class API documentation `). - From a ``Mask1D``,``Grid1D``s can be derived, which represent the (y,x) Cartesian coordinates of a subset of + From a ``Mask1D``, ``Grid1D``s can be derived, which represent the (x,) Cartesian coordinates of a subset of pixels with significance. For example: - - An ``all_false`` ``Grid1D``: the same shape as the original ``Mask1D`` but has unmasked ``False`` values - everywhere. + - An ``all_false`` ``Grid1D``: the (x,) coordinates of every pixel in the ``Mask1D`` regardless of whether + each pixel is masked or unmasked. Parameters ---------- mask - The ``Mask2D`` from which new ``Grid2D`` objects are derived. + The ``Mask1D`` from which new ``Grid1D`` objects are derived. Examples -------- @@ -43,20 +43,14 @@ def __init__(self, mask: Mask1D): import autoarray as aa - mask_2d = aa.Mask2D( - mask=[ - [True, True, True, True, True], - [True, False, False, False, True], - [True, False, False, False, True], - [True, False, False, False, True], - [True, True, True, True, True], - ], + mask_1d = aa.Mask1D( + mask=[True, False, False, False, True], pixel_scales=1.0, ) - derive_grid_2d = aa.DeriveGrid2D(mask=mask_2d) + derive_grid_1d = aa.DeriveGrid1D(mask=mask_1d) - print(derive_grid_2d.border) + print(derive_grid_1d.all_false) """ self.mask = mask @@ -64,21 +58,21 @@ def __init__(self, mask: Mask1D): def all_false(self) -> Grid1D: """ Returns a ``Grid1D`` which uses the ``Mask1D`` - geometry (``shape_native`` / ``pixel_scales`` / ``origin``) and every pixel in the ``Mask2D`` - irrespective of whether pixels are masked or unmasked (given by ``True`` or``False``). + geometry (``shape_native`` / ``pixel_scales`` / ``origin``) and includes every pixel in the ``Mask1D``, + irrespective of whether pixels are masked or unmasked (given by ``True`` or ``False``). - For example, for the following ``Mask2D``: + For example, for the following ``Mask1D``: :: - mask_2d = aa.Mask1D( - mask=[False, False, True, True] + mask_1d = aa.Mask1D( + mask=[False, False, True, True], pixel_scales=1.0, ) The ``all_false`` ``Grid1D`` (given via ``mask_1d.derive_grid.all_false``) is: :: - [-2.0, -1.0, 1.0, 2.0] + [-1.5, -0.5, 0.5, 1.5] Examples -------- @@ -87,7 +81,7 @@ def all_false(self) -> Grid1D: import autoarray as aa - mask_1d = aa.Mask2D( + mask_1d = aa.Mask1D( mask=[False, False, True, True], pixel_scales=1.0, ) diff --git a/autoarray/mask/derive/grid_2d.py b/autoarray/mask/derive/grid_2d.py index 1b3c0fc6a..e7f951f40 100644 --- a/autoarray/mask/derive/grid_2d.py +++ b/autoarray/mask/derive/grid_2d.py @@ -263,7 +263,7 @@ def border(self) -> Grid2D: pixel_scales=1.0, ) - The ``edge`` grid (given via ``mask_2d.derive_grid.edge``) is given by: + The ``border`` grid (given via ``mask_2d.derive_grid.border``) is given by: :: [[3.0, -3.0], [3.0, -2.0], [3.0, -1.0], [3.0, 0.0], [3.0, 1.0], [3.0, 2.0], [ 3.0, 3.0], diff --git a/autoarray/mask/derive/indexes_2d.py b/autoarray/mask/derive/indexes_2d.py index 1a80b42c6..04ff1cf45 100644 --- a/autoarray/mask/derive/indexes_2d.py +++ b/autoarray/mask/derive/indexes_2d.py @@ -340,7 +340,7 @@ def border_native(self) -> np.ndarray: [ [1,1], [1,2], [1,3], [1,4], [1,5], [1,6], [1,7], [2,1], [2,7], [3,1], [3,7], [4,1], [4,7], [5,1], [5,7], [6,1], [6,7], - [7,1], [7,2], 71,3], [7,4], [7,5], [7,6], [7,7] + [7,1], [7,2], [7,3], [7,4], [7,5], [7,6], [7,7] ] The interior 8 ``False`` values (e.g. ``native`` index ``[3,3]``) are omitted, because although they are edge diff --git a/autoarray/mask/derive/mask_1d.py b/autoarray/mask/derive/mask_1d.py index af0dbe9c2..c0813e081 100644 --- a/autoarray/mask/derive/mask_1d.py +++ b/autoarray/mask/derive/mask_1d.py @@ -103,7 +103,7 @@ def to_mask_2d(self) -> Mask2D: :: [True, False, False, False, True] - The corresponding ``Mask2D`` (given via ``mask_1d.derive_mask.to_mask_1d``) is: + The corresponding ``Mask2D`` (given via ``mask_1d.derive_mask.to_mask_2d``) is: :: [[False, False, False, False, False]] diff --git a/autoarray/mask/derive/mask_2d.py b/autoarray/mask/derive/mask_2d.py index ad1e1c558..9216bcf90 100644 --- a/autoarray/mask/derive/mask_2d.py +++ b/autoarray/mask/derive/mask_2d.py @@ -174,6 +174,9 @@ def blurring_from( ---------- kernel_shape_native The 2D shape of the 2D convolution ``Convolver`` which defines the blurring region. + allow_padding + If ``True``, blurring pixels that extend beyond the mask boundary are allowed. If ``False`` (default), + an exception is raised if the blurring region extends beyond the mask boundary. Examples -------- @@ -277,7 +280,7 @@ def edge_buffed(self) -> Mask2D: """ Returns a buffed edge ``Mask2D``, representing all unmasked pixels (given by ``False``) which neighbor any masked value (give by ``True``) and therefore are on the edge of the 2D mask, but with a buffer of 1 pixel - applied such that everu pixel 1 pixel further out are included. + applied such that every pixel 1 pixel further out is included. For example, for the following ``Mask2D``: diff --git a/autoarray/mask/derive/zoom_2d.py b/autoarray/mask/derive/zoom_2d.py index f3a3f5dac..79762199a 100644 --- a/autoarray/mask/derive/zoom_2d.py +++ b/autoarray/mask/derive/zoom_2d.py @@ -22,7 +22,8 @@ def __init__(self, mask: Union[np.ndarray, List]): omitted (for a full description see the :meth:`Mask2D` class API documentation `). - The `Zoom2D` object calculations many different zoomed in qu + The ``Zoom2D`` object computes the zoomed region around the unmasked pixels, including its centre, + pixel offsets, scaled offsets, and extracted sub-arrays for visualization. Parameters ---------- @@ -90,15 +91,14 @@ def centre(self) -> Tuple[float, float]: @property def offset_pixels(self) -> Tuple[float, float]: """ - Returns the offset of the centred of the zoomed in region from the centre of the `Mask2D` object in pixel - units. + Returns the (y,x) pixel offset of the centre of the zoomed region from the centre of the ``Mask2D``. - This is computed by subtracting the pixel coordinates of the `Mask2D` object from the pixel coordinates of - the zoomed in region. + This is the difference between the pixel coordinates of the zoomed region's centre and the pixel + coordinates of the full mask's central pixel. Returns ------- - The offset of the zoomed in region from the centre of the `Mask2D` object in pixel units. + The (y,x) offset of the zoomed region centre from the mask centre in pixel units. """ if self.mask.pixel_scales is None: return self.mask.geometry.central_pixel_coordinates @@ -111,15 +111,13 @@ def offset_pixels(self) -> Tuple[float, float]: @property def offset_scaled(self) -> Tuple[float, float]: """ - Returns the offset of the centred of the zoomed in region from the centre of the `Mask2D` object in scaled - units. + Returns the (y,x) scaled offset of the centre of the zoomed region from the centre of the ``Mask2D``. - This is computed by subtracting the pixel coordinates of the `Mask2D` object from the pixel coordinates of - the zoomed in region. + This converts ``offset_pixels`` to scaled units using the mask's ``pixel_scales``. Returns ------- - The offset of the zoomed in region from the centre of the `Mask2D` object in scaled units. + The (y,x) offset of the zoomed region centre from the mask centre in scaled units. """ return ( -self.mask.pixel_scales[0] * self.offset_pixels[0], @@ -173,17 +171,19 @@ def shape_native(self) -> Tuple[int, int]: def extent_from(self, buffer: int = 1) -> np.ndarray: """ - For an extracted zoomed array computed from the method *zoomed_around_mask* compute its extent in scaled - coordinates. + Compute the extent of the zoomed region in scaled coordinates, including an optional pixel buffer. - 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. + The extent is returned as a tuple of the form ``(x_min, x_max, y_min, y_max)`` which matches the + ``extent`` format expected by ``matplotlib.imshow``. Parameters ---------- buffer - The number pixels around the extracted array used as a buffer. + The number of pixels around the unmasked region used as a buffer when computing the extent. + + Returns + ------- + The extent of the zoomed region as ``(x_min, x_max, y_min, y_max)`` in scaled units. """ from autoarray.mask.mask_2d import Mask2D diff --git a/autoarray/mask/mask_1d.py b/autoarray/mask/mask_1d.py index a4525aa99..b7bc8c590 100644 --- a/autoarray/mask/mask_1d.py +++ b/autoarray/mask/mask_1d.py @@ -53,7 +53,13 @@ def __init__( pixel_scales The scaled units to pixel units conversion factor of each pixel. origin - The x origin of the mask's coordinate system in scaled units. + The (x,) origin of the mask's coordinate system in scaled units. + invert + If `True`, the `bool`'s of the input `mask` are inverted, so `False` entries become `True` + and vice versa. + xp + The array module to use (default `numpy`; pass `jax.numpy` for JAX support). Controls + whether internal index arrays are computed on CPU or GPU. """ if type(mask) is list: @@ -102,10 +108,18 @@ def geometry(self) -> Geometry1D: @property def derive_mask(self) -> DeriveMask1D: + """ + Returns the ``DeriveMask1D`` object associated with the mask, which computes derived masks such as + the edge mask. + """ return DeriveMask1D(mask=self) @property def derive_grid(self) -> DeriveGrid1D: + """ + Returns the ``DeriveGrid1D`` object associated with the mask, which computes derived grids of (x,) + coordinates such as the unmasked pixel grid. + """ return DeriveGrid1D(mask=self) @classmethod @@ -122,9 +136,14 @@ def all_false( Parameters ---------- shape_slim - The (y,x) shape of the mask in units of pixels. + The 1D shape of the mask in units of pixels. pixel_scales The scaled units to pixel units conversion factor of each pixel. + origin + The (x,) scaled units origin of the mask's coordinate system. + invert + If `True`, the `bool`'s of the input `mask` are inverted, so `False` entries become `True` + and vice versa. """ return cls( mask=np.full(shape=shape_slim, fill_value=False), @@ -149,9 +168,16 @@ def from_fits( file_path The full path of the fits file. hdu - The HDU number in the fits file containing the image image. + The HDU number in the ``.fits`` file containing the mask array. pixel_scales The scaled units to pixel units conversion factor of each pixel. + origin + The (x,) scaled units origin of the mask's coordinate system. + + Returns + ------- + Mask1D + The mask loaded from the ``.fits`` file. """ return cls( @@ -164,10 +190,17 @@ def from_fits( @property def shape_native(self) -> Tuple[int]: + """ + The 1D shape of the mask in its native representation, equal to the shape of the underlying boolean ndarray. + """ return self.shape @property def shape_slim(self) -> Tuple[int]: + """ + The 1D shape of the mask in its slim representation. For a 1D mask this is the same as ``shape_native`` + since there is no native/slim distinction — every pixel is on the same 1D line. + """ return self.shape @property @@ -175,8 +208,7 @@ def header_dict(self) -> Dict: """ Returns the pixel scales of the mask as a header dictionary, which can be written to a .fits file. - A 2D mask has different pixel scale variables for each dimension, the header therefore contain both pixel - scales as separate y and x entries. + A 1D mask has a single pixel scale, so the header contains one pixel scale entry alongside the origin. Returns ------- diff --git a/autoarray/mask/mask_2d.py b/autoarray/mask/mask_2d.py index 94c0029c7..1fd5e722e 100644 --- a/autoarray/mask/mask_2d.py +++ b/autoarray/mask/mask_2d.py @@ -199,6 +199,12 @@ def __init__( it is converted to a (float, float) structure. origin The (y,x) scaled units origin of the mask's coordinate system. + invert + If `True`, the `bool`'s of the input `mask` are inverted, so `False` entries become `True` + and vice versa. + xp + The array module to use (default `numpy`; pass `jax.numpy` for JAX support). Controls + whether internal index arrays are computed on CPU or GPU. """ if type(mask) is list: @@ -224,6 +230,13 @@ def __init__( @property def native_for_slim(self): + """ + A 2D array of shape [total_unmasked_pixels, 2] that maps every unmasked pixel's slim index to its + (y, x) native 2D index. + + For example, if ``slim_to_native[3] = [2, 5]``, the 4th unmasked pixel (slim index 3) is located at + row 2, column 5 in the native 2D array. + """ return self.derive_indexes.native_for_slim __no_flatten__ = ("derive_indexes",) @@ -252,18 +265,34 @@ def geometry(self) -> Geometry2D: @property def derive_indexes(self) -> DeriveIndexes2D: + """ + Returns the ``DeriveIndexes2D`` object associated with the mask, which contains derived index arrays + used to map data between ``slim`` (1D unmasked) and ``native`` (2D full-shape) representations. + """ return DeriveIndexes2D(mask=self, xp=self._xp) @property def derive_mask(self) -> DeriveMask2D: + """ + Returns the ``DeriveMask2D`` object associated with the mask, which computes derived masks such as + the edge mask, border mask, and blurring mask. + """ return DeriveMask2D(mask=self) @property def derive_grid(self) -> DeriveGrid2D: + """ + Returns the ``DeriveGrid2D`` object associated with the mask, which computes derived grids of (y,x) + coordinates such as the unmasked pixel grid, edge grid, and border grid. + """ return DeriveGrid2D(mask=self) @property def zoom(self) -> Zoom2D: + """ + Returns the ``Zoom2D`` object associated with the mask, which computes the zoomed region of the mask + around its unmasked pixels for use in visualization. + """ return Zoom2D(mask=self) @classmethod @@ -586,18 +615,29 @@ def from_fits( invert: bool = False, ) -> "Mask2D": """ - Loads the image from a .fits file. + Load a ``Mask2D`` from a 2D boolean array stored in a ``.fits`` file. Parameters ---------- file_path - The full path of the fits file. + The full path of the ``.fits`` file, including the file name and extension. + 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 HDU number in the fits file containing the image image. - pixel_scales or (float, float) - The scaled units to pixel units conversion factor of each pixel. + The HDU number in the ``.fits`` file containing the mask array. origin The (y,x) scaled units origin of the mask's coordinate system. + resized_mask_shape + If provided, the loaded mask is resized to this (y,x) shape after loading. + invert + If `True`, the `bool`'s of the loaded mask are inverted, so `False` entries become `True` + and vice versa. + + Returns + ------- + Mask2D + The mask loaded from the ``.fits`` file. """ pixel_scales = geometry_util.convert_pixel_scales_2d(pixel_scales=pixel_scales) @@ -619,6 +659,9 @@ def from_fits( @property def shape_native(self) -> Tuple[int, ...]: + """ + The 2D shape of the mask in its native representation, equal to the shape of the underlying boolean ndarray. + """ return self.shape @cached_property @@ -689,17 +732,24 @@ def trimmed_array_from(self, padded_array, image_shape) -> Array2D: def unmasked_blurred_array_from(self, padded_array, psf, image_shape) -> Array2D: """ - For a padded grid and psf, compute an unmasked blurred image from an unmasked unblurred image. + Convolve a padded array with the PSF and trim it back to the original image shape. - This relies on using the lens dataset's padded-grid, which is a grid of (y,x) coordinates which extends over - the entire image as opposed to just the masked region. + This relies on a padded array whose shape extends beyond the masked region, so that PSF convolution + does not suffer from edge effects. The result is trimmed back to ``image_shape`` after convolution. Parameters ---------- - psf : aa.Convolver - The PSF of the image used for convolution. - unmasked_image_1d - The 1D unmasked image which is blurred. + padded_array + The padded ``Array2D`` (or slim 1D representation) of values to be convolved with the PSF. + psf + The PSF convolver used to blur the padded array. + image_shape + The (y,x) shape of the original (unpadded) image, used to trim the blurred result. + + Returns + ------- + Array2D + The blurred and trimmed ``Array2D`` of shape ``image_shape``. """ blurred_image = psf.convolved_image_from( @@ -732,6 +782,15 @@ def header_dict(self) -> Dict: @property def mask_centre(self) -> Tuple[float, float]: + """ + The (y,x) scaled coordinate centre of the unmasked pixels in the mask. + + This is computed as the mean of the (y,x) scaled coordinates of all unmasked pixels. + + Returns + ------- + The (y,x) centre of the unmasked region of the mask in scaled units. + """ grid = grid_2d_util.grid_2d_slim_via_mask_from( mask_2d=self, pixel_scales=self.pixel_scales, @@ -812,7 +871,7 @@ def rescaled_from(self, rescale_factor) -> Mask2D: def resized_from(self, new_shape, pad_value: int = 0.0) -> Mask2D: """ - Returns the ``Mask2D`` resized to a small or bigger ``ndarraay``, but with the same distribution of + Returns the ``Mask2D`` resized to a small or bigger ``ndarray``, but with the same distribution of ``False`` and ``True`` entries. Resizing which increases the ``Mask2D`` shape pads it with values on its edge. diff --git a/autoarray/structures/abstract_structure.py b/autoarray/structures/abstract_structure.py index d70a6ceea..81fe34706 100644 --- a/autoarray/structures/abstract_structure.py +++ b/autoarray/structures/abstract_structure.py @@ -29,42 +29,79 @@ def slim(self) -> "Structure": @property def geometry(self): + """ + The geometry object of the mask associated with this structure, which defines coordinate conversions + between pixel units and scaled units. + """ return self.mask.geometry @property def derive_grid(self) -> DeriveGrid2D: + """ + The ``DeriveGrid2D`` object of the mask, used to compute derived grids of (y,x) coordinates such as + the edge grid, border grid, and full unmasked grid. + """ return self.mask.derive_grid @property def derive_indexes(self) -> DeriveIndexes2D: + """ + The ``DeriveIndexes2D`` object of the mask, used to compute index arrays that map data between the + ``slim`` (1D unmasked) and ``native`` (2D full-shape) representations. + """ return self.mask.derive_indexes @property def derive_mask(self) -> DeriveMask2D: + """ + The ``DeriveMask2D`` object of the mask, used to compute derived masks such as the edge mask, + border mask, and blurring mask. + """ return self.mask.derive_mask @property def shape_slim(self) -> int: + """ + The 1D shape of the data structure in its ``slim`` representation, equal to the number of unmasked pixels. + """ return self.mask.shape_slim @property def shape_native(self) -> Tuple[int, ...]: + """ + The shape of the data structure in its ``native`` representation (e.g. ``(total_y_pixels, total_x_pixels)`` + for a 2D structure). + """ return self.mask.shape @property def pixel_scales(self) -> Tuple[float, ...]: + """ + The (y,x) scaled units to pixel units conversion factors of every pixel, as a tuple of floats. + """ return self.mask.pixel_scales @property def pixel_scale(self) -> float: + """ + The pixel scale as a single float value. Assumes all pixel scales are equal (e.g. square pixels). + """ return self.mask.pixel_scale @property def header_dict(self) -> Dict: + """ + The FITS header dictionary of the mask associated with this structure, containing pixel scale and origin entries. + """ return self.mask.header_dict @property def pixel_area(self): + """ + The area of a single pixel in scaled units squared (``pixel_scales[0] * pixel_scales[1]``). + + Only valid for 2D structures; raises ``GridException`` for 1D structures. + """ if len(self.pixel_scales) != 2: raise exc.GridException("Cannot compute area of structure which is not 2D.") @@ -72,19 +109,45 @@ def pixel_area(self): @property def total_area(self): + """ + The total area of all unmasked pixels in scaled units squared (``total_pixels * pixel_area``). + """ return self.total_pixels * self.pixel_area @property def origin(self) -> Tuple[int, ...]: + """ + The (y,x) scaled units origin of the mask's coordinate system. + """ return self.mask.origin @property def unmasked_grid(self) -> Union[Grid1D, Grid2D]: + """ + A grid of (y,x) coordinates of every pixel in the full mask shape (including masked pixels), using + the mask's geometry to compute each pixel's scaled coordinate. + """ return self.mask.derive_grid.all_false @property def total_pixels(self) -> int: + """ + The total number of unmasked pixels in the data structure (its ``slim`` length). + """ return self.shape[0] def trimmed_after_convolution_from(self, kernel_shape) -> "Structure": + """ + Trim the data structure back to its original shape after PSF convolution has been performed on a + padded version of it. + + This is the inverse of ``padded_before_convolution_from``: first the data structure is padded so + that edge-pixel signal is not lost during convolution, and then this method trims the result back + to the original shape. + + Parameters + ---------- + kernel_shape + The 2D shape of the convolution kernel used to pad and trim the data structure. + """ raise NotImplementedError diff --git a/autoarray/structures/arrays/array_2d_util.py b/autoarray/structures/arrays/array_2d_util.py index f186faeca..824db2c79 100644 --- a/autoarray/structures/arrays/array_2d_util.py +++ b/autoarray/structures/arrays/array_2d_util.py @@ -12,7 +12,7 @@ def convert_array(array: Union[np.ndarray, List]) -> np.ndarray: """ - If the input array input a convert is of type list, convert it to type NumPy array. + Convert the input array to a NumPy ``ndarray`` if it is a list, or unwrap it if it is an autoarray object. Parameters ---------- @@ -32,6 +32,16 @@ def convert_array(array: Union[np.ndarray, List]) -> np.ndarray: def check_array_2d(array_2d: np.ndarray): + """ + Check that an array intended for use as an ``Array2D`` slim representation has exactly 1 dimension. + + Raises ``ArrayException`` if the array is not 1D. + + Parameters + ---------- + array_2d + The array to check, which must have shape [total_unmasked_pixels]. + """ if len(array_2d.shape) != 1: raise exc.ArrayException( "An array input into the Array2D.__new__ method is not of shape 1." @@ -281,9 +291,9 @@ def resized_array_2d_from( resized_shape The (y,x) new pixel dimension of the trimmed array. origin - The oigin of the resized array, e.g. the central pixel around which the array is extracted. + The origin of the resized array, e.g. the central pixel around which the array is extracted. pad_value - If the reszied array is bigger in size than the input array, the value the padded edge values are filled in + If the resized array is bigger in size than the input array, the value the padded edge values are filled in using. Returns @@ -405,7 +415,7 @@ def index_slim_for_index_2d_from(indexes_2d: np.ndarray, shape_native) -> np.nda Examples -------- indexes_2d = np.array([[0,0], [1,0], [2,0], [2,2]]) - indexes_flat = index_flat_for_index_2d_from(indexes_2d=indexes_2d, shape=(3,3)) + indexes_slim = index_slim_for_index_2d_from(indexes_2d=indexes_2d, shape_native=(3,3)) """ # Calculate 1D indexes as row_index * number_of_columns + col_index index_slim_for_index_native_2d = ( @@ -434,11 +444,9 @@ def array_2d_slim_from( Parameters ---------- array_2d_native - A 2D array of values on the dimensions of the grid. + A 2D array of values on the dimensions of the grid, with shape [total_y_pixels, total_x_pixels]. mask_2d A 2D array of bools, where `False` values mean unmasked and are included in the mapping. - array_2d_native - The 2D array of values which are mapped to a 1D array. Returns ------- diff --git a/autoarray/structures/arrays/irregular.py b/autoarray/structures/arrays/irregular.py index f4b54ee11..b875d11c3 100644 --- a/autoarray/structures/arrays/irregular.py +++ b/autoarray/structures/arrays/irregular.py @@ -45,6 +45,9 @@ def __init__(self, values: Union[List, np.ndarray]): @property def values(self): + """ + The raw underlying 1D ndarray of values, with shape [total_values]. + """ return self.array @property @@ -56,6 +59,10 @@ def slim(self) -> "ArrayIrregular": @property def native(self) -> Structure: + """ + The ``ArrayIrregular`` in its ``native`` representation. For an irregular array there is no 2D native + format, so this returns the array as-is. + """ return self @property diff --git a/autoarray/structures/arrays/uniform_1d.py b/autoarray/structures/arrays/uniform_1d.py index f9a48b83c..c0306e819 100644 --- a/autoarray/structures/arrays/uniform_1d.py +++ b/autoarray/structures/arrays/uniform_1d.py @@ -25,6 +25,29 @@ def __init__( store_native: bool = False, xp=np, ): + """ + A uniform 1D array of values, paired with a 1D mask of pixels. + + Each entry of an ``Array1D`` corresponds to a value at the centre of a pixel in its corresponding ``Mask1D``. + It is ordered such that pixels begin from the left of the corresponding mask and go right. + + Like ``Array2D``, the ``Array1D`` supports ``slim`` (1D, unmasked only) and ``native`` (1D, full length) + data representations. + + Parameters + ---------- + values + The values of the array, input in the ``slim`` or ``native`` format. + mask + The 1D mask associated with the array, defining which pixels each array value is paired with. + header + Optional metadata header associated with the array (e.g. from a FITS file). + store_native + If True, the ndarray is stored in its native format [total_pixels]. This avoids mapping large data + arrays to and from the slim / native formats, which can be a computational bottleneck. + xp + The array module to use (default ``numpy``; pass ``jax.numpy`` for JAX support). + """ values = array_1d_util.convert_array_1d( array_1d=values, mask_1d=mask, store_native=store_native, xp=xp @@ -66,7 +89,7 @@ def no_mask( array_1d = aa.Array1D.no_mask(values=np.array([1.0, 2.0, 3.0, 4.0]), pixel_scales=1.0) - # Make Array2D from input list. + # Make Array1D from input list. array_1d = aa.Array1D.no_mask(values=[1.0, 2.0, 3.0, 4.0], pixel_scales=1.0) diff --git a/autoarray/structures/arrays/uniform_2d.py b/autoarray/structures/arrays/uniform_2d.py index f6857bda4..c37fc8bc8 100644 --- a/autoarray/structures/arrays/uniform_2d.py +++ b/autoarray/structures/arrays/uniform_2d.py @@ -187,9 +187,15 @@ def __init__( mask The 2D mask associated with the array, defining the pixels each array value in its ``slim`` representation is paired with. + header + Optional metadata header (e.g. from a FITS file) associated with the array. 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. + skip_mask + If True, masking is skipped and values are stored as-is without zeroing masked entries. + xp + The array module to use (default ``numpy``; pass ``jax.numpy`` for JAX support). Examples -------- @@ -334,22 +340,39 @@ def native_skip_mask(self) -> "Array2D": @property def in_counts(self) -> "Array2D": + """ + The array converted from electrons-per-second (eps) to total counts, using the exposure time stored + in the associated ``Header``. + """ return self.header.array_eps_to_counts(array_eps=self) @property def in_counts_per_second(self) -> "Array2D": + """ + The array converted from electrons-per-second (eps) to counts-per-second, via an intermediate + conversion to total counts using the ``Header`` exposure time. + """ return self.header.array_counts_to_counts_per_second( array_counts=self.in_counts ) @property def original_orientation(self) -> Union[np.ndarray, "Array2D"]: + """ + The array rotated back to its original CCD read-out orientation, using the read-out-electronics (ROE) + corner stored in the associated ``Header``. + """ return layout_util.rotate_array_via_roe_corner_from( array=self, roe_corner=self.header.original_roe_corner ) @property def readout_offsets(self) -> Tuple[int, int]: + """ + The (y,x) pixel offsets of the read-out electronics corner, taken from the associated ``Header``. + + Returns ``(0, 0)`` if no header is present or no readout offsets are stored in the header. + """ if self.header is not None: if self.header.readout_offsets is not None: return self.header.readout_offsets @@ -438,10 +461,10 @@ def brightest_sub_pixel_coordinate_in_region_from( For example, if the input region is `region=(-0.15, 0.25, 0.35, 0.55)` the code finds all pixels inside of this region in scaled units, finds the brightest pixel of those pixels, and then on a 3x3 grid surrounding - this pixel determines the locaiton of the brightest sub pixel using a weighted centre calculation. + this pixel determines the location of the brightest sub pixel using a weighted centre calculation. - The centre of the brightest pixel is returned, the function `brightest_sub_pixel_coordinate_in_region_from` - performs a sub pixel calculation to return the brightest sub pixel coordinate. + Unlike ``brightest_coordinate_in_region_from``, which returns the centre of the brightest pixel, this + function performs a weighted-centroid sub-pixel calculation to return a more precise coordinate. Parameters ---------- @@ -621,9 +644,9 @@ def no_mask( # Make Array2D from input list, native format # (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. + array_2d = aa.Array2D.no_mask( + values=np.array([[1.0, 2.0], [3.0, 4.0]]), + pixel_scales=1.0, ) """ diff --git a/autoarray/structures/grids/grid_1d_util.py b/autoarray/structures/grids/grid_1d_util.py index 6fd756d1f..71d1d3e75 100644 --- a/autoarray/structures/grids/grid_1d_util.py +++ b/autoarray/structures/grids/grid_1d_util.py @@ -16,26 +16,28 @@ def convert_grid_1d( grid_1d: Union[np.ndarray, List], mask_1d: Mask1D, store_native: bool = False, xp=np ) -> np.ndarray: """ - The `manual` classmethods in the Grid2D object take as input a list or ndarray which is returned as a Grid2D. + The ``manual`` classmethods in the ``Grid1D`` object take as input a list or ndarray which is returned as a ``Grid1D``. - This function performs the following and checks and conversions on the input: + This function performs the following checks and conversions on the input: - 1: If the input is a list, convert it to an ndarray. - 2: Check that the number of pixels in the array is identical to that of the mask. - 3) Map the input ndarray to its `slim` representation. + 1. If the input is a list, convert it to an ndarray. + 2. Check that the number of pixels in the array is identical to that of the mask. + 3. Map the input ndarray to its ``slim`` representation. - For a Grid2D, `slim` refers to a 2D NumPy array of shape [total_coordinates, 2] and `native` a 3D NumPy array of - shape [total_y_coordinates, total_x_coordinates, 2] + For a ``Grid1D``, ``slim`` refers to a 1D NumPy array of shape [total_unmasked_pixels] and ``native`` a 1D + NumPy array of shape [total_pixels]. Parameters ---------- - grid_2d - The input (y,x) grid of coordinates which is converted to an ndarray if it is a list. - mask_2d - The mask of the output Array2D. + grid_1d + The input (x,) grid of coordinates which is converted to an ndarray if it is a list. + mask_1d + The mask of the output ``Grid1D``. store_native - If True, the ndarray is stored in its native format [total_y_pixels, total_x_pixels, 2]. This avoids - mapping large data arrays to and from the slim / native formats, which can be a computational bottleneck. + If True, the ndarray is stored in its native format [total_pixels]. This avoids mapping large data arrays + to and from the slim / native formats, which can be a computational bottleneck. + xp + The array module to use (default ``numpy``; pass ``jax.numpy`` for JAX support). """ grid_1d = grid_2d_util.convert_grid(grid=grid_1d) @@ -114,20 +116,20 @@ def grid_1d_slim_via_mask_from( A 1D array of bools, where `False` values are unmasked and therefore included as part of the calculated grid. pixel_scales - The (x) scaled units to pixel units conversion factor of the 2D mask array. + The (x,) scaled units to pixel units conversion factor of the 1D mask array. origin - The (x) origin of the 2D array, which the grid is shifted around. + The (x,) origin of the 1D array, which the grid is shifted around. Returns ------- ndarray - A grid of (x) scaled coordinates at the centre of every pixel unmasked pixel on the 2D mask + A grid of (x) scaled coordinates at the centre of every unmasked pixel in the 1D mask array. The grid array has dimensions (total_unmasked_pixels, ). Examples -------- mask = np.array([True, False, True, False, False, False]) - grid_slim = grid_1d_via_mask_from(mask_1d=mask_1d, pixel_scales=(0.5, 0.5), origin=(0.0, 0.0)) + grid_slim = grid_1d_slim_via_mask_from(mask_1d=mask, pixel_scales=(0.5,), origin=(0.0,)) """ centres_scaled = geometry_util.central_scaled_coordinate_1d_from( shape_slim=mask_1d.shape, pixel_scales=pixel_scales, origin=origin diff --git a/autoarray/structures/grids/grid_2d_util.py b/autoarray/structures/grids/grid_2d_util.py index 1ad816935..7fdca9728 100644 --- a/autoarray/structures/grids/grid_2d_util.py +++ b/autoarray/structures/grids/grid_2d_util.py @@ -13,7 +13,14 @@ def convert_grid(grid: Union[np.ndarray, List]) -> np.ndarray: + """ + Convert the input grid to a NumPy ``ndarray`` if it is a list, or unwrap it if it is an autoarray object. + Parameters + ---------- + grid + The grid which may be a list, ndarray, or autoarray object. + """ try: grid = grid.array except AttributeError: @@ -26,6 +33,20 @@ def convert_grid(grid: Union[np.ndarray, List]) -> np.ndarray: def check_grid_slim(grid, shape_native): + """ + Validate that a grid is not in slim format without an accompanying ``shape_native``, and that ``shape_native`` + is a valid 2-tuple of ints. + + Raises ``GridException`` if the grid is 2D (slim) but no ``shape_native`` was provided, or if + ``shape_native`` is not a (int, int) tuple. + + Parameters + ---------- + grid + The input grid to validate. + shape_native + The expected native shape of the grid, required when the input is in slim format. + """ if shape_native is None: raise exc.GridException( f""" @@ -47,6 +68,16 @@ def check_grid_slim(grid, shape_native): def check_grid_2d(grid_2d: np.ndarray): + """ + Validate that a grid has a final dimension of 2 (for (y,x) coordinate pairs) and is either 2D or 3D. + + Raises ``GridException`` if either condition is violated. + + Parameters + ---------- + grid_2d + The grid ndarray to validate. + """ if grid_2d.shape[-1] != 2: raise exc.GridException( "The final dimension of the input grid is not equal to 2 (e.g. the (y,x) coordinates)" @@ -57,6 +88,19 @@ def check_grid_2d(grid_2d: np.ndarray): def check_grid_2d_and_mask_2d(grid_2d: np.ndarray, mask_2d: Mask2D): + """ + Validate that a grid and mask are compatible, i.e. that the grid's shape is consistent with the number of + unmasked pixels (for slim format) or the mask's native shape (for native format). + + Raises ``GridException`` if the shapes are inconsistent. + + Parameters + ---------- + grid_2d + The grid ndarray to validate, either slim [total_unmasked_pixels, 2] or native [y, x, 2]. + mask_2d + The mask to compare against. + """ if len(grid_2d.shape) == 2: if grid_2d.shape[0] != mask_2d.pixels_in_mask: raise exc.GridException( @@ -129,18 +173,18 @@ def convert_grid_2d_to_slim( grid_2d: Union[np.ndarray, List], mask_2d: Mask2D, xp=np ) -> np.ndarray: """ - he `manual` classmethods in the Grid2D object take as input a list or ndarray which is returned as a Grid2D. + The ``manual`` classmethods in the Grid2D object take as input a list or ndarray which is returned as a Grid2D. - This function checks the dimensions of the input `grid_2d` and maps it to its `slim` representation. + This function checks the dimensions of the input ``grid_2d`` and maps it to its ``slim`` representation. - For a Grid2D, `slim` refers to a 2D NumPy array of shape [total_coordinates, 2]. + For a Grid2D, ``slim`` refers to a 2D NumPy array of shape [total_coordinates, 2]. Parameters ---------- grid_2d - The input (y,x) grid of coordinates which is converted to its silm representation. + The input (y,x) grid of coordinates which is converted to its slim representation. mask_2d - The mask of the output Array2D. + The mask of the output Grid2D. """ if len(grid_2d.shape) == 2: return grid_2d @@ -152,18 +196,18 @@ def convert_grid_2d_to_native( mask_2d: Mask2D, ) -> np.ndarray: """ - he `manual` classmethods in the Grid2D object take as input a list or ndarray which is returned as a Grid2D. + The ``manual`` classmethods in the Grid2D object take as input a list or ndarray which is returned as a Grid2D. - This function checks the dimensions of the input `grid_2d` and maps it to its `native` representation. + This function checks the dimensions of the input ``grid_2d`` and maps it to its ``native`` representation. - For a Grid2D, `native` refers to a 2D NumPy array of shape [total_y_coordinates, total_x_coordinates, 2]. + For a Grid2D, ``native`` refers to a 3D NumPy array of shape [total_y_coordinates, total_x_coordinates, 2]. Parameters ---------- grid_2d The input (y,x) grid of coordinates which is converted to its native representation. mask_2d - The mask of the output Array2D. + The mask of the output Grid2D. """ if len(grid_2d.shape) == 3: return grid_2d @@ -417,7 +461,7 @@ def _radial_projected_shape_slim_from( ---------- extent The extent of the grid the radii grid is computed using, with format [xmin, xmax, ymin, ymax] - centre : (float, flloat) + centre : (float, float) 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. @@ -498,7 +542,7 @@ def grid_scaled_2d_slim_radial_projected_from( ---------- extent The extent of the grid the radii grid is computed using, with format [xmin, xmax, ymin, ymax] - centre : (float, flloat) + centre : (float, float) 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. @@ -570,7 +614,7 @@ def grid_2d_slim_from( ---------- grid_2d_native : ndarray The native grid of (y,x) values which are mapped to the slimmed grid. - mask_2d + mask A 2D array of bools, where `False` values mean unmasked and are included in the mapping. Returns diff --git a/autoarray/structures/grids/irregular_2d.py b/autoarray/structures/grids/irregular_2d.py index bd07006c7..1cbd3ac3e 100644 --- a/autoarray/structures/grids/irregular_2d.py +++ b/autoarray/structures/grids/irregular_2d.py @@ -75,6 +75,9 @@ def from_pixels_and_mask( @property def values(self): + """ + The raw underlying ndarray of (y,x) coordinate pairs, with shape [total_coordinates, 2]. + """ return self._array @property @@ -106,10 +109,18 @@ def geometry(self): @property def slim(self) -> "Grid2DIrregular": + """ + Returns the grid in its ``slim`` representation. For an irregular grid there is no 2D native format, + so this returns the grid as-is. + """ return self @property def native(self) -> "Grid2DIrregular": + """ + Returns the grid in its ``native`` representation. For an irregular grid there is no 2D native format, + so this returns the grid as-is. + """ return self @property @@ -177,7 +188,7 @@ def squared_distances_to_coordinate_from( self, coordinate: Tuple[float, float] = (0.0, 0.0) ) -> ArrayIrregular: """ - Returns the squared distance of every (y,x) coordinate in this *Coordinate* instance from an input + Returns the squared distance of every (y,x) coordinate in this ``Grid2DIrregular`` instance from an input coordinate. Parameters @@ -194,7 +205,7 @@ def distances_to_coordinate_from( self, coordinate: Tuple[float, float] = (0.0, 0.0) ) -> ArrayIrregular: """ - Returns the distance of every (y,x) coordinate in this *Coordinate* instance from an input coordinate. + Returns the distance of every (y,x) coordinate in this ``Grid2DIrregular`` instance from an input coordinate. Parameters ---------- diff --git a/autoarray/structures/grids/uniform_1d.py b/autoarray/structures/grids/uniform_1d.py index ee7e78a72..6dbcd51e5 100644 --- a/autoarray/structures/grids/uniform_1d.py +++ b/autoarray/structures/grids/uniform_1d.py @@ -76,7 +76,8 @@ def __init__( __native__ - The Grid2D has the same properties as Case 1, but is stored as an an ndarray of shape [total_x_coordinates]. + The ``Grid1D`` in its ``native`` format is stored as an ndarray of shape [total_x_coordinates], where + masked entries have a value of 0.0. All masked entries on the grid has (y,x) values of (0.0, 0.0). @@ -111,10 +112,13 @@ def __init__( Parameters ---------- values - The (y,x) coordinates of the grid. + The (x,) coordinates of the grid, input as an ndarray of shape [total_unmasked_pixels] or + [total_pixels] (native format). mask - The 2D mask associated with the grid, defining the pixels each grid coordinate is paired with and - originates from. + The 1D mask associated with the grid, defining which pixels each grid coordinate is paired with. + store_native + If True, the ndarray is stored in its native format [total_pixels]. This avoids mapping large data + arrays to and from the slim / native formats, which can be a computational bottleneck. """ values = grid_1d_util.convert_grid_1d( @@ -138,28 +142,28 @@ def no_mask( Parameters ---------- values - The (y,x) coordinates of the grid input as an ndarray of shape [total_unmasked_pixels, 2] - or a list of lists. + The (x,) coordinates of the grid, input as an ndarray of shape [total_unmasked_pixels] + or a list of values. pixel_scales - The (y,x) arcsecond-to-pixel units conversion factor of every pixel. If this is input as a `float`, - it is converted to a (float, float). + The (x,) arcsecond-to-pixel units conversion factor of every pixel. If this is input as a ``float``, + it is converted to a (float,) tuple. origin - The origin of the grid's mask. + The (x,) origin of the grid's coordinate system. Examples -------- .. code-block:: python - import autogrid as aa + import autoarray as aa - # Make Grid1D from input np.ndgrid. + # Make Grid1D from input ndarray. - grid_1d = aa.Grid1D.no_mask(grid=np.grid([1.0, 2.0, 3.0, 4.0]), pixel_scales=1.0) + grid_1d = aa.Grid1D.no_mask(values=np.array([1.0, 2.0, 3.0, 4.0]), pixel_scales=1.0) - # Make Grid2D from input list. + # Make Grid1D from input list. - grid_1d = aa.Grid1D.no_mask(grid=[1.0, 2.0, 3.0, 4.0], pixel_scales=1.0) + grid_1d = aa.Grid1D.no_mask(values=[1.0, 2.0, 3.0, 4.0], pixel_scales=1.0) # Print grid's slim (masked 1D data representation) and # native (masked 1D data representation) diff --git a/autoarray/structures/visibilities.py b/autoarray/structures/visibilities.py index 8759f2c95..52dc7a148 100644 --- a/autoarray/structures/visibilities.py +++ b/autoarray/structures/visibilities.py @@ -191,7 +191,7 @@ def from_fits(cls, file_path: Union[Path, str], hdu: int) -> "Visibilities": The path the file is loaded from, including the filename and the ``.fits`` extension, e.g. '/path/to/filename.fits' hdu - The Header-Data Unit of the .fits file the visibilitiy data is loaded from. + The Header-Data Unit of the .fits file the visibility data is loaded from. """ visibilities_1d = ndarray_via_fits_from(file_path=file_path, hdu=hdu) return cls(visibilities=visibilities_1d)