import numpy as np
from pathlib import Path
from typing import Optional, Union, Tuple, List
from autoconf.fitsable import ndarray_via_fits_from, header_obj_from
from autoarray.structures.header import Header
from autoarray.structures.abstract_structure import Structure
from autoarray.structures.grids.uniform_1d import Grid1D
from autoarray.mask.mask_1d import Mask1D
from autoarray.structures.arrays import array_1d_util
from autoarray.structures.arrays import array_2d_util
from autoarray.geometry import geometry_util
from autoarray import type as ty
[docs]
class Array1D(Structure):
def __init__(
self,
values: Union[np.ndarray, List],
mask: Mask1D,
header: Optional[Header] = None,
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
)
self.mask = mask
self.header = header
super().__init__(values, xp=xp)
[docs]
@classmethod
def no_mask(
cls,
values: Union[np.ndarray, Tuple[float], List[float]],
pixel_scales: ty.PixelScales,
origin: Tuple[float] = (0.0,),
header: Optional[Header] = None,
) -> "Array1D":
"""
Create a Array1D (see `Array1D.__new__`) by inputting the array values in 1D
Parameters
----------
values
The values of the array input as an ndarray of shape [total_unmasked_pixels] or a list.
pixel_scales
The scaled units to pixel units conversion factor of the array data coordinates (e.g. the x-axis).
origin
The origin of the 1D array's mask.
Examples
--------
.. code-block:: python
import autoarray as aa
# Make Array1D from input np.ndarray.
array_1d = aa.Array1D.no_mask(values=np.array([1.0, 2.0, 3.0, 4.0]), pixel_scales=1.0)
# Make Array1D from input list.
array_1d = aa.Array1D.no_mask(values=[1.0, 2.0, 3.0, 4.0], pixel_scales=1.0)
# Print array's slim (masked 1D data representation) and
# native (masked 1D data representation)
print(array_1d.slim)
print(array_1d.native)
"""
values = array_2d_util.convert_array(values)
pixel_scales = geometry_util.convert_pixel_scales_1d(pixel_scales=pixel_scales)
mask = Mask1D.all_false(
shape_slim=values.shape[0],
pixel_scales=pixel_scales,
origin=origin,
)
return Array1D(values=np.array(values), mask=mask, header=header)
[docs]
@classmethod
def full(
cls,
fill_value: float,
shape_native: Union[int, Tuple[int]],
pixel_scales: ty.PixelScales,
origin: Tuple[float] = (0.0,),
header: Optional[Header] = None,
) -> "Array1D":
"""
Create an `Array1D` (see `Array1D.__new__`) where all values are filled with an input fill value,
analogous to the method np.full().
From 1D input the method cannot determine the 1D shape of the array and its mask, thus the `shape_native` must
be input into this method. The mask is setup as a unmasked `Mask1D` of size `shape_native`.
Parameters
----------
fill_value
The value all array elements are filled with.
shape_native : Tuple[int]
The 1D shape of the mask the array is paired with.
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,) structure.
origin : (float,)
The (x) scaled units origin of the mask's coordinate system.
"""
shape_native = geometry_util.convert_shape_native_1d(shape_native=shape_native)
return cls.no_mask(
values=np.full(fill_value=fill_value, shape=shape_native[0]),
pixel_scales=pixel_scales,
origin=origin,
header=header,
)
[docs]
@classmethod
def zeros(
cls,
shape_native: Union[int, Tuple[int]],
pixel_scales: ty.PixelScales,
origin: Tuple[float] = (0.0,),
header: Optional[Header] = None,
) -> "Array1D":
"""
Create an `Array1D` (see `Array1D.__new__`) where all values are filled with zeros, analogous to the
method np.zeros().
From 1D input the method cannot determine the 1D shape of the array and its mask, thus the `shape_native` must
be input into this method. The mask is setup as a unmasked `Mask1D` of size `shape_native`.
Parameters
----------
shape_native : Tuple[int]
The 1D shape of the mask the array is paired with.
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,) structure.
origin : (float,)
The (x) scaled units origin of the mask's coordinate system.
"""
return cls.full(
fill_value=0.0,
shape_native=shape_native,
pixel_scales=pixel_scales,
origin=origin,
header=header,
)
[docs]
@classmethod
def ones(
cls,
shape_native: Union[int, Tuple[int]],
pixel_scales: ty.PixelScales,
origin: Tuple[float] = (0.0,),
header: Optional[Header] = None,
) -> "Array1D":
"""
Create an `Array1D` (see `Array1D.__new__`) where all values are filled with ones, analogous to the
method np.ones().
From 1D input the method cannot determine the 1D shape of the array and its mask, thus the `shape_native` must
be input into this method. The mask is setup as a unmasked `Mask1D` of size `shape_native`.
Parameters
----------
shape_native : Tuple[int]
The 1D shape of the mask the array is paired with.
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,) structure.
origin : (float,)
The (x) scaled units origin of the mask's coordinate system.
"""
return cls.full(
fill_value=1.0,
shape_native=shape_native,
pixel_scales=pixel_scales,
origin=origin,
header=header,
)
[docs]
@classmethod
def from_fits(
cls,
file_path: Union[Path, str],
pixel_scales: ty.PixelScales,
hdu: int = 0,
origin: Tuple[float] = (0.0, 0.0),
) -> "Array1D":
"""
Create an Array1D (see `Array1D.__new__`) by loading the array values from a .fits file.
Parameters
----------
file_path
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 array data is loaded from.
pixel_scales
The (x,) scaled units to pixel units conversion factors of every pixel. If this is input as a float,
it is converted to a (float,) structure.
origin
The (x,) scaled units origin of the coordinate system.
"""
array_1d = ndarray_via_fits_from(file_path=file_path, hdu=hdu)
header_sci_obj = header_obj_from(file_path=file_path, hdu=0)
header_hdu_obj = header_obj_from(file_path=file_path, hdu=hdu)
return cls.no_mask(
values=array_1d.astype(
"float64"
), # Have to do this due to typing issues in 1D with astorpy fits.
pixel_scales=pixel_scales,
origin=origin,
header=Header(header_sci_obj=header_sci_obj, header_hdu_obj=header_hdu_obj),
)
@property
def slim(self) -> "Array1D":
"""
Return an `Array1D` where the data is stored its `slim` representation, which is an ndarray of shape
[total_unmasked_pixels].
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 `Array1D`.
"""
return Array1D(values=self, mask=self.mask)
@property
def native(self) -> "Array1D":
"""
Return an `Array1D` where the data is stored in its `native` representation, which is an ndarray of shape
[total_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 `Array1D`.
"""
return Array1D(values=self, mask=self.mask, store_native=True)
@property
def readout_offsets(self) -> Tuple[float]:
if self.header is not None:
if self.header.readout_offsets is not None:
return self.header.readout_offsets
return (0,)
@property
def grid_radial(self) -> Grid1D:
return Grid1D.uniform_from_zero(
shape_native=self.shape_native,
pixel_scales=self.pixel_scales,
)