Source code for hsr4hci.units

"""
Methods related to using units and quantities from ``astropy.units``.
"""

# -----------------------------------------------------------------------------
# IMPORTS
# -----------------------------------------------------------------------------

from contextlib import nullcontext
from typing import Any, Union

from astropy import units

import numpy as np


# -----------------------------------------------------------------------------
# CLASS DEFINITIONS
# -----------------------------------------------------------------------------

[docs]class InstrumentUnitsContext: r""" Context manager that allows to provide local, instrument-specific values for the ``pixscale`` and ``lambda_over_d`` to the registry of ``astropy.units``. In order to convert between pixels and arcseconds, one has to define a pixel scale; this value, however, is instrument-specific. Similarly, the value of $\lambda / D$ obviously depends both on the wavelength $\lambda$ of the observation and the diameter $D$ of the primary mirror. If we want to use $\lambda / D$ as a unit (as it is a characteristic scale, e.g., for the PSF size), we need to define its value in terms of other units somewhere. This class provides a context manager that is initialized with values for the pixel scale and $\lambda / D$, and it then provides a (re-usable!) context inside of which an :py:class:`astropy.units.Quantity` can be converted freely between units of pixels, arc seconds and $\lambda / D$. Args: pixscale: An :py:class:`astropy.units.Quantity` with units `arcsec / pixel` that defines the pixel scale. .. admonition:: Example For VLT/NACO, the pixscale is typically: ``PIXSCALE = 0.0271 arcsec / pixel``. lambda_over_d: An :py:class:`astropy.units.Quantity` with units `arc seconds` that defines the ratio between the wavelength $\lambda$ of the observation and $D$, the diameter of the primary mirror. .. admonition:: Example For *L'*-band data ($\lambda$ = 3800 nm) at the VLT ($D$ = 8.2 m), this value is: ``lambda_over_d = 0.0956 arcsec``. """
[docs] @units.quantity_input( pixscale=units.Unit('arcsec / pixel'), lambda_over_d=units.Unit('arcsec'), ) def __init__( self, pixscale: units.Quantity, lambda_over_d: units.Quantity, ) -> None: # Store the values of the pixel scale and lambda over D self.pixel_scale = units.pixel_scale(pixscale) self.lod_unit = units.def_unit( s=['lod', 'lambda_over_d'], represents=lambda_over_d ) # Initialize contexts for units and equivalencies (as empty contexts) self.context_units = nullcontext() self.context_equivalencies = nullcontext()
def __enter__(self) -> None: # (Re)-create contexts both for the unit (i.e., lambda_over_d) and the # equivalency (i.e., pixel and arcseconds). # We cannot create these contexts in the constructor, because they are # not re-usable, meaning we cannot simply re-enter them after having # used their __exit__() method. By re-creating these contexts everytime # we call __enter__() on the InstrumentUnitsContext, we ensure that the # latter is indeed re-usable. self.context_units = units.add_enabled_units(self.lod_unit) self.context_equivalencies = units.add_enabled_equivalencies( self.pixel_scale ) # Enter the unit conversion contexts we have just created self.context_equivalencies.__enter__() self.context_units.__enter__() def __exit__(self, *exc_details: Any) -> None: # Exit the unit conversion context in the order in which we entered self.context_units.__exit__(*exc_details) self.context_equivalencies.__exit__(*exc_details)
# ----------------------------------------------------------------------------- # FUNCTION DEFINITIONS # -----------------------------------------------------------------------------
[docs]def flux_ratio_to_magnitudes( flux_ratio: Union[float, np.ndarray] ) -> Union[float, np.ndarray]: """ Convert a given flux ratio to magnitudes. Example: >>> flux_ratio_to_magnitudes(1e-5) 12.5 Args: flux_ratio: The brightness (or contrast) as a flux ratio; either as a single float or as a numpy array of floats. Returns: The brightness (or contrast) in magnitudes. """ if isinstance(flux_ratio, np.ndarray): return np.asarray(-2.5 * np.log10(flux_ratio)) return -2.5 * float(np.log10(flux_ratio))
[docs]def magnitude_to_flux_ratio( magnitudes: Union[float, np.ndarray] ) -> Union[float, np.ndarray]: """ Convert magnitudes to a flux ratio. Example: >>> magnitude_to_flux_ratio(5) 0.01 Args: magnitudes: The brightness (or contrast) in magnitudes; either as a single float or as a numpy array of floats. Returns: The brightness (or contrast) as a flux ratio. """ return 10 ** (-magnitudes / 2.5)