from __future__ import annotations # "type1 | type2" syntax in Python <3.9
import numpy as np
import numpy.typing as npt
from lightcurvelynx.astro_utils.mag_flux import mag2flux
[docs]
def sky_bg_adu_to_electrons(sky_bg_adu, gain):
"""Convert sky background from ADU/pixel to electrons/pixel.
Parameters
----------
sky_bg_adu : float or ndarray of float
Sky background in ADU/pixel.
gain : float or ndarray of float
The CCD gain (in e-/ADU).
Returns
-------
float or ndarray of float
Sky background in electrons/pixel.
"""
return sky_bg_adu * gain
[docs]
def magnitude_electron_zeropoint(
*,
filter: npt.ArrayLike,
airmass: npt.ArrayLike,
exptime: npt.ArrayLike,
instr_zp_mag: dict[str, float] | float | npt.ArrayLike,
ext_coeff: dict[str, float] | float | npt.ArrayLike,
) -> npt.ArrayLike:
"""Photometric zeropoint (magnitude that produces 1 electron) for
LSST bandpasses (v1.9), using a standard atmosphere scaled
for different airmasses and scaled for exposure times.
Parameters
----------
filter : ndarray of str
The filter for which to return the photometric zeropoint.
airmass : ndarray of float
The airmass at which to return the photometric zeropoint.
exptime : ndarray of float
The exposure time for which to return the photometric zeropoint.
instr_zp_mag : dict[str, float], float, or ndarray of float
The instrumental zeropoint for each bandpass,
i.e. AB-magnitude that produces 1 electron in a 1-second exposure.
Keys are the bandpass names, values are the zeropoints.
ext_coeff : dict[str, float], float, or ndarray of float
Atmospheric extinction coefficient for each bandpass.
Keys are the bandpass names, values are the coefficients.
Returns
-------
ndarray of float
AB mags that produces 1 electron.
Note
----
Typically, zeropoints are defined as the magnitude of a source
which would produce 1 count in a 1 second exposure -
here we use *electron* counts, not ADU counts.
References
----------
Lynne Jones - https://community.lsst.org/t/release-of-v3-4-simulations/8548/12
"""
# If we are given dictionaries mapping filter to value for either instr_zp_mag or ext_coeff,
# convert them to the corresponding array of values for the input filter array.
if isinstance(instr_zp_mag, dict):
instr_zp_mag = np.vectorize(instr_zp_mag.get)(filter)
if isinstance(ext_coeff, dict):
ext_coeff = np.vectorize(ext_coeff.get)(filter)
return instr_zp_mag + ext_coeff * (airmass - 1) + 2.5 * np.log10(exptime)
[docs]
def flux_electron_zeropoint(
*,
instr_zp_mag: dict[str, float] | float | npt.ArrayLike,
ext_coeff: dict[str, float] | float | npt.ArrayLike,
filter: npt.ArrayLike,
airmass: npt.ArrayLike,
exptime: npt.ArrayLike,
) -> npt.ArrayLike:
"""Flux (nJy) producing 1 electron.
Parameters
----------
filter : npt.ArrayLike
The filter for which to return the photometric zeropoint.
airmass : npt.ArrayLike
The airmass at which to return the photometric zeropoint.
exptime : npt.ArrayLike
The exposure time for which to return the photometric zeropoint.
instr_zp_mag : dict[str, float], float, or ndarray of float
The instrumental zeropoint for each bandpass in AB magnitudes,
i.e. the magnitude that produces 1 electron in a 1-second exposure.
Keys are the bandpass names, values are the zeropoints.
ext_coeff : dict[str, float], float, or ndarray of float
Atmospheric extinction coefficient for each bandpass.
Keys are the bandpass names, values are the coefficients.
Returns
-------
ndarray of float
Flux (nJy) per electron.
"""
mag_zp_electron = magnitude_electron_zeropoint(
instr_zp_mag=instr_zp_mag,
ext_coeff=ext_coeff,
filter=filter,
airmass=airmass,
exptime=exptime,
)
return mag2flux(mag_zp_electron)
[docs]
def calculate_zp_from_maglim(
maglim=None,
sky_bg_electrons=None,
fwhm_px=None,
read_noise=None,
dark_current=None,
exptime=None,
nexposure=1,
):
"""Calculate zero points based on the 5-sigma mag limit.
Calculated according to formulas::
snr = flux/fluxerr
fluxerr = sqrt( flux + sky*npix*gain + readnoise**2*nexposure*npix
+ darkcurrent*npix*exptime*nexposure)
5 = flux/fluxerr
25 = flux**2/( flux + sky*npix*Gain + readnoise**2*nexposure*npix
+ darkcurrent*npix*exptime*nexposure)
flux**2 - 25*flux -25*( sky*npix*Gain
+ readnoise**2*nexposure*npix
+ darkcurrent*npix*exptime*nexposure)
= 0
flux = 12.5 + 0.5*sqrt(625
+ 100( sky*npix*Gain
+ readnoise**2*nexposure*npix
+ darkcurrent*npix*exptime*nexposure) )
zp = 2.5*log10(flux) + maglim
Parameters
----------
maglim : float or ndarray
Five-sigma magnitude limit.
sky_bg_electrons : float or ndarray
Sky background in electrons/pixel.
fwhm_px : float or ndarray
PSF in pixels.
read_noise : float or ndarray
Read noise (in e-/pixel).
dark_current : float or ndarray
Dark current (in e-/pixel/second).
exptime : float or ndarray
Exposure time (in seconds).
nexposure : int or ndarray
Number of exposure. Default is 1.
Returns
-------
zp: float or ndarray
Instrument zero point (that converts 1 e- to magnitude).
"""
npix = 2.266 * fwhm_px**2 # = 4 * pi * sigma**2 = pi/2/ln2 * FWHM**2
flux_at_5sigma_limit = 12.5 + 2.5 * np.sqrt(
25.0
+ 4.0
* (
sky_bg_electrons * npix
+ read_noise**2 * nexposure * npix
+ dark_current * npix * exptime * nexposure
)
)
zp = 2.5 * np.log10(flux_at_5sigma_limit) + maglim
return zp