예제 #1
0
def test_circle_correct_area():
    x, y = coordinates.make_xy_grid(256, diameter=2)
    r, _ = coordinates.cart_to_polar(x, y)
    mask = geometry.circle(1, r)
    expected_area_of_circle = x.size * 3.14
    # sum is integer quantized, binary mask, allow one half quanta of error
    assert pytest.approx(mask.sum(), expected_area_of_circle, abs=0.5)
예제 #2
0
def test_qcon_zzprime_q2d():
    # decent number of points, so that finite diff isn't awful
    x, y = coordinates.make_xy_grid(512, diameter=2)
    r, t = coordinates.cart_to_polar(x, y)
    coefs_c = np.random.rand(5)
    coefs_a = np.random.rand(4, 4)
    coefs_b = np.random.rand(4, 4)
    z, zprimer, zprimet = polynomials.qpoly.compute_z_zprime_Q2d(
        coefs_c, coefs_a, coefs_b, r, t)
    delta = x[0, 1] - x[0, 0]
    ddy, ddx = np.gradient(z, delta)
    dx, dy = surface_normal_from_cylindrical_derivatives(
        zprimer, zprimet, r, t)
    dx = fix_zero_singularity(dx, x, y)
    dy = fix_zero_singularity(dy, x, y)

    # apply this mask, otherwise the very large gradients outside the unit disk
    # make things look terrible.
    # even at 512x512, the relative error is very large at the edge of the unit
    # circle, hence the enormous rtol that works out to about 25%
    mask = r < 1
    dx *= mask
    dy *= mask
    ddx *= mask
    ddy *= mask
    assert np.allclose(dx, ddx, atol=1)
    assert np.allclose(dy, ddy, atol=1)
예제 #3
0
 def FFp(x, y):
     # TODO: significantly cheaper without t?
     r, t = cart_to_polar(x, y, vec_to_grid=False)
     rsq = r * r
     z = conic_sag(params['c'], params['k'], rsq)
     dr = conic_sag_der(params['c'], params['k'], r)
     dx, dy = surface_normal_from_cylindrical_derivatives(dr, 0, r, t)
     return z, dx, dy
예제 #4
0
 def FFp(x, y):
     r, t = cart_to_polar(x, y, vec_to_grid=False)
     c, k, dx, dy = params['c'], params['k'], params['dx'], params['dy']
     z = off_axis_conic_sag(c, k, r, t, dx=dx, dy=dy)
     dr, dt = off_axis_conic_der(c, k, r, t, dx=dx, dy=dy)
     ddx, ddy = surface_normal_from_cylindrical_derivatives(
         dr, dt, r, t)
     return z, ddx, ddy
예제 #5
0
def test_truecircle_correct_area():
    # this test is identical to the test for circle.  The tested accuracy is
    # 10x finer since this mask shader is not integer quantized
    x, y = coordinates.make_xy_grid(256, diameter=2)
    r, _ = coordinates.cart_to_polar(x, y)
    mask = geometry.truecircle(1, r)
    expected_area_of_circle = x.size * 3.14
    # sum is integer quantized, binary mask, allow one half quanta of error
    assert pytest.approx(mask.sum(), expected_area_of_circle, abs=0.05)
예제 #6
0
def tpsf_mutate():
    x = y = np.linspace(-LIM, LIM, SAMPLES)
    xx, yy = np.meshgrid(x, y)
    rho, phi = cart_to_polar(xx, yy)
    dat = psf.airydisk(rho, 10, 0.55)
    _psf = psf.PSF(data=dat, x=x, y=y)
    _psf.fno = 10
    _psf.wavelength = 0.55
    return _psf
예제 #7
0
def test_diffprop_matches_airydisk(efl, epd, wvl):
    fno = efl / epd
    x, y = make_xy_grid(128, diameter=epd)
    r, t = cart_to_polar(x, y)
    amp = circle(epd/2, r)
    wf = Wavefront.from_amp_and_phase(amp/amp.sum(), None, wvl, x[0, 1] - x[0, 0])
    psf = wf.focus(efl, Q=3)
    s = psf.intensity.slices()
    u_, sx = s.x
    u_, sy = s.y
    analytic = airydisk(u_, fno, wvl)
    assert np.allclose(sx, analytic, atol=PRECISION)
    assert np.allclose(sy, analytic, atol=PRECISION)
예제 #8
0
    def _gengrid(self):
        ''' Generates a uniform (x,y) grid and maps it to (rho,phi) coordinates
            for radial polynomials.

            Note:
                angle is done via cart_to_polar(yv, xv) which yields angles
                w.r.t. the y axis.  This is the convention of optics and not a
                typo.
        '''
        x = y = linspace(-1, 1, self.samples, dtype=config.precision)
        xv, yv = meshgrid(x, y)
        self.rho, self.phi = cart_to_polar(yv, xv)
        return self.rho, self.phi
예제 #9
0
    def __init__(self,
                 num_spokes,
                 sinusoidal=True,
                 background='black',
                 sample_spacing=2,
                 samples=384):
        ''' Produces a Siemen's Star.

        Args:
            num_spokes (`int`): number of spokes in the star.

            sinusoidal (`bool`): if True, generates a sinusoidal Siemen' star.
                If false, generates a bar/block siemen's star.

            background ('string'): "black" or "white".

            sample_spacing (`float`): Spacing of samples, in microns.

            samples (`int`): number of samples per dimension in the synthetic image.

        '''
        relative_width = 0.9
        self.num_spokes = num_spokes

        # generate a coordinate grid
        x = np.linspace(-1, 1, samples)
        y = np.linspace(-1, 1, samples)
        xx, yy = np.meshgrid(x, y)
        rv, pv = cart_to_polar(xx, yy)

        # generate the siemen's star as a (rho,phi) polynomial
        arr = cos(num_spokes / 2 * pv)

        if not sinusoidal:  # make binary
            arr[arr < 0] = -1
            arr[arr > 0] = 1

        # scale to (0,1) and clip into a disk
        arr = (arr + 1) / 2
        if background.lower() in ('b', 'black'):
            arr[rv > relative_width] = 0
        elif background.lower() in ('w', 'white'):
            arr[rv > relative_width] = 1
        else:
            raise ValueError('invalid background color')

        super().__init__(data=arr,
                         sample_spacing=sample_spacing,
                         synthetic=True)
예제 #10
0
def test_diffprop_matches_analyticmtf(efl, epd, wvl):
    fno = efl / epd
    x, y = make_xy_grid(128, diameter=epd)
    r, t = cart_to_polar(x, y)
    amp = circle(epd/2, r)
    wf = Wavefront.from_amp_and_phase(amp, None, wvl, x[0, 1] - x[0, 0])
    psf = wf.focus(efl, Q=3).intensity
    mtf = mtf_from_psf(psf.data, psf.dx)
    s = mtf.slices()
    u_, sx = s.x
    u_, sy = s.y

    analytic_1 = diffraction_limited_mtf(fno, wvl, frequencies=u_)
    analytic_2 = diffraction_limited_mtf(fno, wvl, frequencies=u_)
    assert np.allclose(analytic_1, sx, atol=PRECISION)
    assert np.allclose(analytic_2, sy, atol=PRECISION)
예제 #11
0
def test_array_orientation_consistency_tilt():
    """The pupil array should be shaped as arr[y,x], as should the psf and MTF.

        A linear phase error in the pupil along y should cause a motion of the
        PSF in y.  Specifically, for a positive-signed phase, that should cause
        a shift in the +y direction.
    """
    N = 128
    wvl = .5
    Q = 3
    x, y = make_xy_grid(N, diameter=2.1)
    r, t = cart_to_polar(x, y)
    amp = circle(1, r)
    wf = Wavefront.from_amp_and_phase(amp, None, wvl, x[0, 1] - x[0, 0])
    psf = wf.focus(1, Q=Q).intensity
    idx_y, idx_x = np.unravel_index(psf.data.argmax(), psf.data.shape)  # row-major y, x
    assert idx_x == (N*Q) // 2
    assert idx_y > N // 2
예제 #12
0
def tpsf():
    x = y = np.linspace(-LIM, LIM, SAMPLES)
    xx, yy = np.meshgrid(x, y)
    rho, phi = cart_to_polar(xx, yy)
    dat = psf.airydisk(rho, 10, 0.55)
    return psf.PSF(data=dat, x=x, y=y)
예제 #13
0
def phi():
    rho, phi = cart_to_polar(X, Y)
    return phi
예제 #14
0
def rho():
    rho, phi = cart_to_polar(X, Y)
    return rho
예제 #15
0
def test_cart_to_polar(x, y):
    rho, phi = coordinates.cart_to_polar(x, y)
    assert np.allclose(rho, sqrt(x**2 + y**2))
    assert np.allclose(phi, arctan2(y, x))
예제 #16
0
def Q2d_and_der(cm0, ams, bms, x, y, normalization_radius, c, k, dx=0, dy=0):
    """Q-type freeform surface, with base (perhaps shifted) conicoic.

    Parameters
    ----------
    cm0 : iterable
        surface coefficients when m=0 (inside curly brace, top line, Eq. B.1)
        span n=0 .. len(cms)-1 and mus tbe fully dense
    ams : iterable of iterables
        ams[0] are the coefficients for the m=1 cosine terms,
        ams[1] for the m=2 cosines, and so on.  Same order n rules as cm0
    bms : iterable of iterables
        same as ams, but for the sine terms
        ams and bms must be the same length - that is, if an azimuthal order m
        is presnet in ams, it must be present in bms.  The azimuthal orders
        need not have equal radial expansions.

        For example, if ams extends to m=3, then bms must reach m=3
        but, if the ams for m=3 span n=0..5, it is OK for the bms to span n=0..3,
        or any other value, even just [0].
    x : numpy.ndarray
        X coordinates
    y : numpy.ndarray
        Y coordinates
    normalization_radius : float
        radius by which to normalize rho to produce u
    c : float
        curvature, reciprocal radius of curvature
    k : float
        kappa, conic constant
    rhosq : numpy.ndarray
        squared radial coordinate (non-normalized)
    dx : float
        shift of the base conic in x
    dy : float
        shift of the base conic in y

    Returns
    -------
    numpy.ndarray, numpy.ndarray, numpy.ndarray
        sag, dsag/drho, dsag/dtheta

    """
    # Q portion
    r, t = cart_to_polar(x, y)
    r2 = r / normalization_radius
    # content of curly braces in B.1 from oe-20-3-2483
    z, zprimer, zprimet = compute_z_zprime_Q2d(cm0, ams, bms, r2, t)

    base_sag = off_axis_conic_sag(c, k, r, t, dx, dy)
    base_primer, base_primet = off_axis_conic_der(c, k, r, t, dx, dy)

    # Eq. 5.1/5.2
    sigma = off_axis_conic_sigma(c, k, r, t, dx, dy)
    sigma = 1 / sigma
    sigmaprimer, sigmaprimet = off_axis_conic_sigma_der(c, k, r, t, dx, dy)

    zprimer /= normalization_radius
    zprimer2 = product_rule(sigma, z, sigmaprimer, zprimer)
    zprimet2 = product_rule(sigma, z, sigmaprimet, zprimet)
    z *= sigma
    z += base_sag
    zprimer2 += base_primer
    zprimet2 += base_primet
    return z, zprimer2, zprimet2
예제 #17
0
"""Tests for detector modeling capabilities."""
import pytest

import numpy as np

from prysm import detector, coordinates

import matplotlib as mpl
mpl.use('Agg')

SAMPLES = 128

x, y = coordinates.make_xy_grid(SAMPLES, dx=1)
r, t = coordinates.cart_to_polar(x, y)


def test_pixel_shades_properly():
    px = detector.pixel(x, y, 10, 10)
    # 121 samples should be white, 5 row/col on each side of zero, plus zero,
    # = 11x11 = 121
    assert px.sum() == 121


def test_analytic_fts_function():
    # these numbers have no meaning, and the sense of x and y is wrong.  Just
    # testing for crashes.
    # TODO: more thorough tests
    olpf_ft = detector.olpf_ft(x, y, 1.234, 4.567)
    assert olpf_ft.any()
    pixel_ft = detector.pixel_ft(x, y, 9.876, 5.4321)
    assert pixel_ft.any()
예제 #18
0
def test_cart_to_polar(x, y):
    rho, phi = coordinates.cart_to_polar(x, y, vec_to_grid=False)
    assert np.allclose(rho, np.sqrt(x**2 + y**2))
    assert np.allclose(phi, np.arctan2(y, x))
oversampling = ceil(2 / Q)
Q_forward = round(Q * oversampling, 1)

# Intermediate higher res gives psize = pixel_pitch/oversampling
psize = pixel_pitch / oversampling

# PSF_domain_res will be output_res*oversampling
# Pupil domain samples will be (PSF_domain_res)/Q_forward
samples = ceil(output_res * oversampling / Q_forward)

# Find pupil dx from wanted psize
pup_dx = psf_sample_to_pupil_sample(psize, samples, wlen, f)

# Construct pupil grid, convert to polar, construct normalized r for phase
xi, eta = make_xy_grid(samples, dx=pup_dx)
r, theta = cart_to_polar(xi, eta)
norm_r = r / lens_R

# Construct amplitude function of pupil function
amp = circle(lens_R, r)
amp = amp / amp.sum()

# Construct phase mode
aber = zernike_nm(4, 0, norm_r, theta)  # spherical aberration
# Scale phase mode to desired opd
phase = aber * wlen / 16 * 1e3

# Construct pupil function from amp and phase functions, propagate to PSF plane, take square modulus.
P = Wavefront.from_amp_and_phase(amp, phase, wlen, pup_dx)
coherent_PSF = P.focus(f, Q=Q_forward)
PSF = coherent_PSF.intensity