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)
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)
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
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
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)
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
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)
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
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)
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)
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
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)
def phi(): rho, phi = cart_to_polar(X, Y) return phi
def rho(): rho, phi = cart_to_polar(X, Y) return rho
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))
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
"""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()
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