# Path: batoid/utils.py
# def bilinear_fit(ux, uy, kx, ky):
#     a = np.empty((len(ux), 3), dtype=float)
#     a[:,0] = 1
#     a[:,1] = ux
#     a[:,2] = uy
#     b = np.empty((len(ux), 2), dtype=float)
#     b[:,0] = kx
#     b[:,1] = ky
#     x, _, _, _ = np.linalg.lstsq(a, b, rcond=-1)
#     return x
# 
# def fieldToDirCos(u, v, projection='postel'):
#     """Convert field angle to direction cosines using specified projection.
# 
#     Parameters
#     ----------
#     u, v : float
#         Tangent plane coordinates in radians.
#     projection : {'postel', 'zemax', 'gnomonic', 'stereographic', 'lambert', 'orthographic'}
#         Projection used to convert field angle to direction cosines.
# 
#     Returns
#     -------
#     alpha, beta, gamma : float
#         Direction cosines (unit vector projected onto x, y, z in order)
# 
#     Notes
#     -----
#     The tangent plane reference is at (u,v) = (0,0), which corresponds to
#     (alpha, beta, gamma) = (0, 0, -1) (a ray coming directly from above).  The
#     orientation is such that vx (vy) is positive when u (v) is positive.
#     """
#     if projection == 'postel':
#         return postelToDirCos(u, v)
#     elif projection == 'zemax':
#         return zemaxToDirCos(u, v)
#     elif projection == 'gnomonic':
#         return gnomonicToDirCos(u, v)
#     elif projection == 'stereographic':
#         return stereographicToDirCos(u, v)
#     elif projection == 'lambert':
#         return lambertToDirCos(u, v)
#     elif projection == 'orthographic':
#         return orthographicToDirCos(u, v)
#     else:
#         raise ValueError("Bad projection: {}".format(projection))


import numpy as np
import batoid
    import galsim
    import galsim
    import galsim
    import galsim
from .utils import bilinear_fit, fieldToDirCos
    from numbers import Real
    optic : batoid.Optic
        Optical system
    theta_x, theta_y : float
        Field angle in radians
    wavelength : float
        Wavelength in meters
    nrad : int, optional
        Number of ray radii to use.  (see RayVector.asPolar())
    naz : int, optional
        Approximate number of azimuthal angles in outermost ring.  (see
        RayVector.asPolar())
    projection : {'postel', 'zemax', 'gnomonic', 'stereographic', 'lambert', 'orthographic'}
        Projection used to convert field angle to direction cosines.

    Returns
    -------
    drdth : (2, 2), ndarray
        Jacobian transformation matrix for converting between (theta_x, theta_y)
        and (x, y) on the focal plane.

    Notes
    -----
        This is the Jacobian of pixels -> tangent plane, (and importantly, not
        pixels -> ra/dec).  It should be *close* to the inverse plate scale
        though, especially near the center of the tangent plane projection.
    """
    # We just use a finite difference approach here.
    dth = 1e-5

    # Make direction cosine vectors

# Path: batoid/utils.py
# def bilinear_fit(ux, uy, kx, ky):
#     a = np.empty((len(ux), 3), dtype=float)
#     a[:,0] = 1
#     a[:,1] = ux
#     a[:,2] = uy
#     b = np.empty((len(ux), 2), dtype=float)
#     b[:,0] = kx
#     b[:,1] = ky
#     x, _, _, _ = np.linalg.lstsq(a, b, rcond=-1)
#     return x
# 
# def fieldToDirCos(u, v, projection='postel'):
#     """Convert field angle to direction cosines using specified projection.
# 
#     Parameters
#     ----------
#     u, v : float
#         Tangent plane coordinates in radians.
#     projection : {'postel', 'zemax', 'gnomonic', 'stereographic', 'lambert', 'orthographic'}
#         Projection used to convert field angle to direction cosines.
# 
#     Returns
#     -------
#     alpha, beta, gamma : float
#         Direction cosines (unit vector projected onto x, y, z in order)
# 
#     Notes
#     -----
#     The tangent plane reference is at (u,v) = (0,0), which corresponds to
#     (alpha, beta, gamma) = (0, 0, -1) (a ray coming directly from above).  The
#     orientation is such that vx (vy) is positive when u (v) is positive.
#     """
#     if projection == 'postel':
#         return postelToDirCos(u, v)
#     elif projection == 'zemax':
#         return zemaxToDirCos(u, v)
#     elif projection == 'gnomonic':
#         return gnomonicToDirCos(u, v)
#     elif projection == 'stereographic':
#         return stereographicToDirCos(u, v)
#     elif projection == 'lambert':
#         return lambertToDirCos(u, v)
#     elif projection == 'orthographic':
#         return orthographicToDirCos(u, v)
#     else:
#         raise ValueError("Bad projection: {}".format(projection))


# Path: batoid/analysis.py
import numpy as np
import batoid
    import galsim
    import galsim
    import galsim
    import galsim
from .utils import bilinear_fit, fieldToDirCos
    from numbers import Real

    optic : batoid.Optic
        Optical system
    theta_x, theta_y : float
        Field angle in radians
    wavelength : float
        Wavelength in meters
    nrad : int, optional
        Number of ray radii to use.  (see RayVector.asPolar())
    naz : int, optional
        Approximate number of azimuthal angles in outermost ring.  (see
        RayVector.asPolar())
    projection : {'postel', 'zemax', 'gnomonic', 'stereographic', 'lambert', 'orthographic'}
        Projection used to convert field angle to direction cosines.

    Returns
    -------
    drdth : (2, 2), ndarray
        Jacobian transformation matrix for converting between (theta_x, theta_y)
        and (x, y) on the focal plane.

    Notes
    -----
        This is the Jacobian of pixels -> tangent plane, (and importantly, not
        pixels -> ra/dec).  It should be *close* to the inverse plate scale
        though, especially near the center of the tangent plane projection.
    """
    # We just use a finite difference approach here.
    dth = 1e-5

    # Make direction cosine vectors

    nominalCos = fieldToDirCos(theta_x, theta_y, projection=projection)