Source code for astrophot.models.airy
from ..utils.decorators import ignore_numpy_warnings, combine_docstrings
from .psf_model_object import PSFModel
from .mixins import RadialMixin
from ..param import forward
from ..backend_obj import backend, ArrayLike
__all__ = ("AiryPSF",)
[docs]
@combine_docstrings
class AiryPSF(RadialMixin, PSFModel):
"""The Airy disk is an analytic description of the diffraction pattern
for a circular aperture.
WARNING: This model does not work in JAX (it doesn't have the required Bessel function implemented)
WARNING: PyTorch appears to have an issue with gradients wrt the R1 parameter. Optimization doesn't seem to work for this model (maybe try scipy optimize?).
The diffraction pattern is described exactly by the configuration of the
lens system under the assumption that all elements are perfect. This
expression goes as:
.. math::
I(\\theta) = I_0\\left[\\frac{2J_1(x)}{x}\\right]^2
.. math::
x = ka\\sin(\\theta) = \\frac{2\\pi a r}{\\lambda R}
where :math:`I(\\theta)` is the intensity as a function of the angular
position within the diffraction system along its main axis, :math:`I_0` is
the central intensity of the airy disk, :math:`J_1` is the Bessel function
of the first kind of order one, :math:`k = \\frac{2\\pi}{\\lambda}` is the
wavenumber of the light, :math:`a` is the aperture radius, :math:`r` is the
radial position from the center of the pattern, :math:`R` is the distance
from the circular aperture to the observation plane.
In the ``AiryPSF`` class we combine the parameters :math:`a,R,\\lambda` and
scale based on the first zero of the :math:`J_1` function. This way you can
work with the more intuitive radius parameter.
:param I0: The central intensity of the airy disk in flux/pix^2.
:param R1: The radius of the first zero of the airy disk in pix.
"""
_model_type = "airy"
_parameter_specs = {
"I0": {
"units": "flux/pix^2",
"value": 1.0,
"shape": (),
"dynamic": False,
"description": "The central intensity of the airy disk in flux/pix^2.",
},
"R1": {
"units": "pix",
"shape": (),
"dynamic": True,
"description": "The radius of the first zero of the airy disk in pix.",
},
}
usable = True
[docs]
@ignore_numpy_warnings
def initialize(self):
super().initialize()
if self.I0.initialized and self.R1.initialized:
return
icenter = self.target.targpixel_to_mypixel(*self.center.value)
if not self.I0.initialized:
mid_chunk = self.target._data[
int(icenter[0]) - 2 : int(icenter[0]) + 2,
int(icenter[1]) - 2 : int(icenter[1]) + 2,
]
self.I0.value = backend.mean(mid_chunk) / self.target.upsample**2
if not self.R1.initialized:
self.R1.value = 2.0
[docs]
@forward
def radial_model(self, R: ArrayLike, I0: ArrayLike, R1: ArrayLike) -> ArrayLike:
x = 3.8317 * R / R1
return I0 * (2 * backend.bessel_j1(x) / x) ** 2