예제 #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_rectangle_correct_area():
    # really this test should be done for a rectangle that is less than the
    # entire array
    x, y = coordinates.make_xy_grid(256, diameter=2)
    mask = geometry.rectangle(1, x, y)
    expected = x.size
    assert mask.sum() == expected
예제 #3
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)
예제 #4
0
def test_thinlens_hopkins_agree():
    # F/10 beam
    x, y = coordinates.make_xy_grid(128, diameter=10)
    dx = x[0, 1] - x[0, 0]
    r = np.hypot(x, y)
    amp = geometry.circle(5, r)
    phs = polynomials.hopkins(0, 2, 0, r / 5, 0, 1) * (
        1.975347661 * HeNe * 1000)  # 1000, nm to um
    wf = propagation.Wavefront.from_amp_and_phase(amp, phs, HeNe, dx)

    # easy case is to choose thin lens efl = 10,000
    # which will result in an overall focal length of 99.0 mm
    # solve defocus delta z relation, then 1000 = 8 * .6328 * 100 * x
    #                                  x = 1000 / 8 / .6328 / 100
    #                                    = 1.975347661
    psf = wf.focus(efl=100, Q=2).intensity

    no_phs_wf = propagation.Wavefront.from_amp_and_phase(amp, None, HeNe, dx)
    # bea
    tl = propagation.Wavefront.thin_lens(10_000, HeNe, x, y)
    wf = no_phs_wf * tl
    psf2 = wf.focus(efl=100, Q=2).intensity

    # lo and behold all ye who read this test, the lies of physical optics modeling
    # did the beam propagate 100, or 99 millimeters?
    # is the PSF we're looking at in the z=100 plane, or the z=99 plane?
    # the answer is simply a matter of interpretation,
    # if the phase screen for the thin lens is in your mind as a way of going
    # to z=99, then we are in the z=99 plane.
    # if the lens is really there, we are in the z=100 plane.
    assert np.allclose(psf.data, psf2.data, rtol=1e-5)
예제 #5
0
def test_rotated_ellipse(maj, min, majang):
    x, y = coordinates.make_xy_grid(32, diameter=2)
    assert type(
        geometry.rotated_ellipse(x=x,
                                 y=y,
                                 width_major=maj,
                                 width_minor=min,
                                 major_axis_angle=majang)) is np.ndarray
예제 #6
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)
예제 #7
0
def test_segmented_hex_functions():
    x, y = coordinates.make_xy_grid(256, diameter=2)
    csa = segmented.CompositeHexagonalAperture(x,
                                               y,
                                               2,
                                               0.2,
                                               .007,
                                               exclude=(0, ))
    nms = [polynomials.noll_to_nm(j) for j in [1, 2, 3]]
    csa.prepare_opd_bases(polynomials.zernike_nm_sequence, nms)
    csa.compose_opd(np.random.rand(len(csa.segment_ids), len(nms)))
    assert csa
예제 #8
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)
예제 #9
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)
예제 #10
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
예제 #11
0
def test_sum_and_lstsq():
    x, y = make_xy_grid(100, diameter=2)
    ns = [0, 1, 2, 3, 4, 5]
    ms = [1, 2, 3, 4, 5, 6, 7]
    weights_x = np.random.rand(len(ns))
    weights_y = np.random.rand(len(ms))
    # "fun" thing, mix first and second kind chebyshev polynomials
    mx, my = polynomials.separable_2d_sequence(ns, ms, x, y,
                                               polynomials.cheby1_sequence,
                                               polynomials.cheby2_sequence)

    data = polynomials.sum_of_xy_modes(mx, my, x, y, weights_x, weights_y)
    mx = [polynomials.mode_1d_to_2d(m, x, y, 'x') for m in mx]
    my = [polynomials.mode_1d_to_2d(m, x, y, 'y') for m in my]
    modes = mx + my  # concat
    exp = list(weights_x) + list(weights_y)  # concat
    coefs = polynomials.lstsq(modes, data)
    assert np.allclose(coefs, exp)
예제 #12
0
def test_generate_spider_doesnt_error(vanes):
    x, y = coordinates.make_xy_grid(32, diameter=2)
    mask = geometry.spider(vanes, 1, x, y)
    assert isinstance(mask, np.ndarray)
예제 #13
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()
예제 #14
0
    def __init__(self,
                 ifn,
                 Nact=50,
                 sep=10,
                 shift=(0, 0),
                 rot=(0, 0, 0),
                 upsample=1,
                 spline_order=3,
                 mask=None):
        """Create a new DM model.

        This model is based on convolution of a 'poke lattice' with the influence
        function.  It has the following idiosyncracies:

            1.  The poke lattice is always "FFT centered" on the array, i.e.
                centered on the sample which would contain the DC frequency bin
                after an FFT.
            2.  The rotation is applied in the same sampling as ifn
            3.  Shifts and resizing are applied using a Fourier method and not
                subject to quantization

        Parameters
        ----------
        ifn : numpy.ndarray
            influence function; assumes the same for all actuators and must
            be the same shape as (x,y).  Assumed centered on N//2th sample of x, y.
            Assumed to be well-conditioned for use in convolution, i.e.
            compact compared to the array holding it
        Nact : int or tuple of int, length 2
            (X, Y) actuator counts
        sep : int or tuple of int, length 2
            (X, Y) actuator separation, samples of influence function
        shift : tuple of float, length 2
            (X, Y) shift of the actuator grid to (x, y), units of x influence
            function sampling.  E.g., influence function on 0.1 mm grid, shift=1
            = 0.1 mm shift.  Positive numbers describe (rightward, downward)
            shifts in image coordinates (origin lower left).
        rot : tuple of int, length <= 3
            (Z, Y, X) rotations; see coordinates.make_rotation_matrix
        upsample : float
            upsampling factor used in determining output resolution, if it is different
            to the resolution of ifn.
        mask : numpy.ndarray
            boolean ndarray of shape Nact used to suppress/delete/exclude
            actuators; 1=keep, 0=suppress

        """
        if isinstance(Nact, int):
            Nact = (Nact, Nact)
        if isinstance(sep, int):
            sep = (sep, sep)

        x, y = make_xy_grid(ifn.shape, dx=1)

        # stash inputs and some computed values on self
        self.ifn = ifn
        self.Ifn = fft.fft2(ifn)
        self.Nact = Nact
        self.sep = sep
        self.shift = shift
        self.obliquity = truenp.cos(truenp.radians(truenp.linalg.norm(rot)))
        self.rot = rot
        self.upsample = upsample

        # prepare the poke array and supplimentary integer arrays needed to
        # copy it into the working array
        out = prepare_actuator_lattice(ifn.shape,
                                       Nact,
                                       sep,
                                       mask,
                                       dtype=x.dtype)
        self.mask = out['mask']
        self.actuators = out['actuators']
        self.actuators_work = np.zeros_like(self.actuators)
        self.poke_arr = out['poke_arr']
        self.ixx = out['ixx']
        self.iyy = out['iyy']

        # rotation data
        self.rotmat = make_rotation_matrix(rot)
        XY = apply_rotation_matrix(self.rotmat, x, y)
        XY2 = xyXY_to_pixels((x, y), XY)
        self.XY = XY
        self.XY2 = XY2
        self.needs_rot = True
        if np.allclose(rot, [0, 0, 0]):
            self.needs_rot = False

        # shift data
        if shift[0] != 0 or shift[1] != 0:
            # caps = Fourier variable (x -> X, y -> Y)
            # make 2pi/px phase ramps in 1D (much faster)
            # then broadcast them to 2D when they're used as transfer functions
            # in a Fourier convolution
            Y, X = [forward_ft_unit(1, s, shift=False) for s in x.shape]
            Xramp = np.exp(X * (-2j * np.pi * shift[0]))
            Yramp = np.exp(Y * (-2j * np.pi * shift[1]))
            shpx = x.shape
            shpy = tuple(reversed(x.shape))
            Xramp = np.broadcast_to(Xramp, shpx)
            Yramp = np.broadcast_to(Yramp, shpy).T
            self.Xramp = Xramp
            self.Yramp = Yramp
            self.tf = [self.Ifn * self.Xramp * self.Yramp]
        else:
            self.tf = [self.Ifn]
예제 #15
0
def test_rectangle_doesnt_break_angle():
    x, y = coordinates.make_xy_grid(16, diameter=2)
    mask = geometry.rectangle(1, x, y, angle=45)
    assert mask.any()
예제 #16
0
def test_offset_circle():
    # [-16, 15] grid
    x, y = coordinates.make_xy_grid(32, dx=1)
    c = geometry.offset_circle(3, x, y, center=(2, 2))
    s = c.sum()
    assert s == 29  # 29 = roundup of 3^2 * pi
예제 #17
0
def test_regular_polygon(sides, samples):
    x, y = coordinates.make_xy_grid(samples, diameter=2)
    mask = geometry.regular_polygon(sides, 1, x, y)
    assert isinstance(mask, np.ndarray)
    assert mask.shape == (samples, samples)
# Need Q_forward >= 2 for forward model, so Q_forward = Q*(oversampling) = 2
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)
예제 #19
0
def test_gaussian(sigma, samples):
    x, y = coordinates.make_xy_grid(samples, diameter=2)
    assert type(geometry.gaussian(sigma, x, y)) is np.ndarray