from __future__ import annotations
from astropy.io import fits
import logging
import numpy as np
from pathlib import Path
from typing import List, Tuple, Union
from autoarray.mask.abstract_mask import Mask
from autoarray.mask.derive.grid_1d import DeriveGrid1D
from autoarray.mask.derive.mask_1d import DeriveMask1D
from autoarray.geometry.geometry_1d import Geometry1D
from autoarray.structures.abstract_structure import Structure
from autoarray.structures.arrays import array_1d_util
from autoarray import exc
from autoarray import type as ty
logging.basicConfig()
logger = logging.getLogger(__name__)
[docs]
class Mask1D(Mask):
def __init__(
self,
mask: Union[np.ndarray, List],
pixel_scales: ty.PixelScales,
origin: Tuple[float,] = (0.0,),
invert: bool = False,
):
"""
A 1D mask, representing 1D data on a uniform line of pixels with equal spacing.
When applied to 1D data it extracts or masks the unmasked image pixels corresponding to mask entries that
are `False` or 0).
The mask also defines the geometry of the 1D data structure it is paired to, for example how every pixel
coordinate on the 1D line of data converts to physical units via the `pixel_scales` and `origin`
parameters and a grid which is used for performing calculations.
Parameters
----------
mask
The ndarray of shape [total_pixels] containing the bool's representing the mask, where `False`
signifies an entry is unmasked and used in calculations.
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.
"""
if type(mask) is list:
mask = np.asarray(mask).astype("bool")
if invert:
mask = np.invert(mask)
if type(pixel_scales) is float:
pixel_scales = (pixel_scales,)
if len(mask.shape) != 1:
raise exc.MaskException("The input mask is not a one dimensional array")
# noinspection PyArgumentList
super().__init__(
mask=mask,
pixel_scales=pixel_scales,
origin=origin,
)
def __array_finalize__(self, obj):
super().__array_finalize__(obj=obj)
if isinstance(obj, Mask1D):
pass
else:
self.origin = (0.0,)
@property
def native(self) -> Structure:
raise NotImplemented()
@property
def geometry(self) -> Geometry1D:
"""
Return the 1D geometry of the mask, representing its uniform rectangular grid of (x) coordinates defined by
its ``shape_native``.
"""
return Geometry1D(
shape_native=self.shape_native,
pixel_scales=self.pixel_scales,
origin=self.origin,
)
@property
def derive_mask(self) -> DeriveMask1D:
return DeriveMask1D(mask=self)
@property
def derive_grid(self) -> DeriveGrid1D:
return DeriveGrid1D(mask=self)
[docs]
@classmethod
def all_false(
cls,
shape_slim,
pixel_scales: ty.PixelScales,
origin: Tuple[float] = (0.0,),
invert: bool = False,
) -> "Mask1D":
"""
Setup a 1D mask where all pixels are unmasked.
Parameters
----------
shape_slim
The (y,x) shape of the mask in units of pixels.
pixel_scales
The scaled units to pixel units conversion factor of each pixel.
"""
return cls(
mask=np.full(shape=shape_slim, fill_value=False),
pixel_scales=pixel_scales,
origin=origin,
invert=invert,
)
[docs]
@classmethod
def from_fits(
cls,
file_path: Union[Path, str],
pixel_scales: ty.PixelScales,
hdu: int = 0,
origin: Tuple[float] = (0.0,),
) -> "Mask1D":
"""
Loads the 1D mask from a .fits file.
Parameters
----------
file_path
The full path of the fits file.
hdu
The HDU number in the fits file containing the image image.
pixel_scales
The scaled units to pixel units conversion factor of each pixel.
"""
return cls(
array_1d_util.numpy_array_1d_via_fits_from(file_path=file_path, hdu=hdu),
pixel_scales=pixel_scales,
origin=origin,
)
[docs]
@classmethod
def from_primary_hdu(
cls,
primary_hdu: fits.PrimaryHDU,
origin: Tuple[float, float] = (0.0, 0.0),
) -> "Mask1D":
"""
Returns an ``Mask1D`` 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 ``Mask1D`` objects, including a description of the ``slim`` and ``native`` attribute
used by the API, see
the :meth:`Mask1D class API documentation <autoarray.structures.arrays.uniform_1d.AbstractMask1D.__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`.
origin
The (y,x) scaled units origin of the coordinate system.
Examples
--------
.. code-block:: python
from astropy.io import fits
import autoarray as aa
primary_hdu = fits.open("path/to/file.fits")
array_1d = aa.Mask1D.from_primary_hdu(
primary_hdu=primary_hdu,
)
"""
return cls(
mask=primary_hdu.data.astype("bool"),
pixel_scales=primary_hdu.header["PIXSCALE"],
origin=origin,
)
@property
def shape_native(self) -> Tuple[int]:
return self.shape
@property
def shape_slim(self) -> Tuple[int]:
return self.shape
@property
def hdu_for_output(self) -> fits.PrimaryHDU:
"""
The mask as a 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 `Array1D.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_1d_util.hdu_for_output_from(
array_1d=self.astype("float"), header_dict=self.pixel_scale_header
)
[docs]
def output_to_fits(self, file_path: Union[Path, str], overwrite: bool = False):
"""
Write the 1D mask to a .fits file.
Parameters
----------
file_path
The full path of the file that is output, including the file name and .fits extension.
overwrite
If `True` and a file already exists with the input file_path the .fits file is overwritten. If `False`,
an error is raised.
Returns
-------
None
Examples
--------
mask = Mask1D(mask=np.full(shape=(5,), fill_value=False))
mask.output_to_fits(file_path='/path/to/file/filename.fits', overwrite=True)
"""
array_1d_util.numpy_array_1d_to_fits(
array_1d=self.astype("float"),
file_path=file_path,
overwrite=overwrite,
header_dict=self.pixel_scale_header,
)