def test_pupil_orientations_before_and_after_focus(plot=False, npix_pupil=128, npix_fov=128): """ Verify pupil orientations before and after focus, and signs of thin lens defocus 1. A negative weak lens produces images (before focus) that have consistent orientation with the exit pupil 2. A positive weak lens produces images (after focus) that have the opposite orientation as the exit pupil 3. Images with the same magnitude but opposite signs of defocus should be 180 degree rotations of one another. """ wave0 = poppy.Wavefront(diam=3 * u.m, npix=npix_pupil) wave0 *= poppy.LetterFAperture() wave1 = wave0.copy() wave2 = wave0.copy() wave1 *= poppy.ThinLens(nwaves=-5) wave1.propagate_to( poppy.Detector(fov_pixels=npix_fov, pixelscale=0.03 * u.arcsec / u.pixel)) wave2 *= poppy.ThinLens(nwaves=+5) wave2.propagate_to( poppy.Detector(fov_pixels=npix_fov, pixelscale=0.03 * u.arcsec / u.pixel)) if plot: fig, axes = plt.subplots(figsize=(15, 6), ncols=3) plt.suptitle("Before and After Focus sign test (Fresnel propagation)", fontweight='bold') wave0.display(ax=axes[0]) wave1.display(imagecrop=fov, title='Intensity at plane before focus', scale='log', ax=axes[1]) wave2.display(imagecrop=fov, title='Intensity at plane after focus', scale='log', ax=axes[2]) # check entrance pupil orientation assert brighter_top_half(wave0.intensity) and brighter_left_half( wave0.intensity), "Letter F should be brighter at top and left" # check orientation of what should be an image before focus assert brighter_top_half(wave1.intensity) and brighter_left_half( wave1.intensity ), "Image with negative lens (before focus) should have same orientation as the pupil " # check orientation of what should be an image after focus assert (not brighter_top_half(wave2.intensity)) and ( not brighter_left_half(wave2.intensity) ), "Image with positive lens (after focus) should have opposite orientation as the pupil " # check consistency of defocus diffraction pattern on either side of focus, just with opposite signs (for this no-WFE case) assert np.allclose( wave1.intensity, np.rot90(wave2.intensity, 2) ), "Positive and negative weak lenses should be 180 degree rotation of one another"
def test_CompoundOpticalSystem_fresnel(npix=128, display=False): """ Test that the CompoundOpticalSystem container works for Fresnel systems Parameters ---------- npix : int Number of pixels for the pupil sampling. Kept small by default to reduce test run time. """ import poppy opt1 = poppy.SquareAperture() opt2 = poppy.CircularAperture(radius=0.55) # a single optical system osys = poppy.FresnelOpticalSystem(beam_ratio=0.25, npix=npix) osys.add_optic(opt1) osys.add_optic(opt2, distance=10 * u.cm) osys.add_optic(poppy.QuadraticLens(1.0 * u.m)) osys.add_optic(poppy.Detector(pixelscale=0.25 * u.micron / u.pixel, fov_pixels=512), distance=1 * u.m) psf = osys.calc_psf(display_intermediates=display) if display: plt.figure() # a Compound Fresnel optical system osys1 = poppy.FresnelOpticalSystem(beam_ratio=0.25, npix=npix) osys1.add_optic(opt1) osys2 = poppy.FresnelOpticalSystem(beam_ratio=0.25) osys2.add_optic(opt2, distance=10 * u.cm) osys2.add_optic(poppy.QuadraticLens(1.0 * u.m)) osys2.add_optic(poppy.Detector(pixelscale=0.25 * u.micron / u.pixel, fov_pixels=512), distance=1 * u.m) cosys = poppy.CompoundOpticalSystem([osys1, osys2]) psf2 = cosys.calc_psf(display_intermediates=display) assert np.allclose( psf[0].data, psf2[0].data ), "Results from simple and compound Fresnel systems differ unexpectedly." return psf, psf2
def test_TipTiltStage(display=False, verbose=False): """ Test tip tilt stage moves the PSF by the requested amount """ ap = poppy.HexagonAperture(flattoflat=0.75 * u.m) det = poppy.Detector(pixelscale=0.1 * u.arcsec / u.pix, fov_pixels=128) tt = poppy.active_optics.TipTiltStage(ap, include_factor_of_two=False) wave = poppy.Wavefront(npix=128, diam=1 * u.m) trans = ap.get_transmission(wave) assert np.allclose(tt.get_transmission(wave), trans), "Transmission does not match expectations" assert np.allclose(tt.get_opd(wave), 0), "OPD without tilt does not match expectation" for tx, ty in ((0 * u.arcsec, 1 * u.arcsec), (1 * u.arcsec, 0 * u.arcsec), (-0.23 * u.arcsec, 0.65 * u.arcsec)): for include_factor_of_two in [True, False]: if verbose: print( f"Testing {tx}, {ty}, with include_factor_of_two={include_factor_of_two}" ) tt.include_factor_of_two = include_factor_of_two tt.set_tip_tilt(tx, ty) wave = poppy.Wavefront(npix=64, diam=1 * u.m) wave *= ap wave *= tt if display: plt.figure() wave.display(what='both') plt.suptitle(f"Wavefront with {tx}, {ty}") wave.propagate_to(det) if display: plt.figure() wave.display() plt.title(f"PSF with {tx}, {ty}") cen = poppy.measure_centroid(wave.as_fits(), boxsize=5, relativeto='center', units='arcsec') factor = 2 if include_factor_of_two else 1 assert np.isclose(cen[1] * u.arcsec, tx * factor, atol=1e-4), "X pos not as expected" assert np.isclose( cen[0] * u.arcsec, ty * factor, atol=1e-4 ), f"Y pos not as expected: {cen[0]*u.arcsec}, {ty*factor}"
def test_wavefront_tilt_sign_and_direction(plot=False, npix=128): """ Test that tilt with increasing WFE towards the +X direction moves the PSF in the -X direction Fraunhofer propagation version See also test_core.test_source_offsets_in_OpticalSystem """ # Create a wavefront and apply a tilt wave = poppy.Wavefront(diam=1 * u.m, npix=npix) wave *= poppy.CircularAperture(radius=0.5 * u.m) tilt_angle = -0.2 # must be a negative number (for -X direction shift), and within the FOV wave.tilt( Xangle=tilt_angle ) # for this function, the input is the desired direction for the image to tilt. # A shift to -X is implemented by creating an OPD that increases toward +X n = wave.shape[0] assert wave.wfe[n // 2, n // 2 - 5] < wave.wfe[n // 2, n // 2 + 5], "Wavefront error should increase to +X" if plot: plt.suptitle("Wavefront tilt sign test (Fraunhofer propagation)", fontweight='bold') wave.display(what='both') wave.propagate_to(poppy.Detector(pixelscale=0.05, fov_pixels=128)) if plot: plt.figure() wave.display(what='both', crosshairs=True, imagecrop=2) n = wave.shape[0] cen = poppy.measure_centroid(wave.as_fits()) assert np.allclose(cen[0], (n - 1) / 2), "Tilt in X should not displace the PSF in Y" assert cen[1] < ( n - 1) / 2, "WFE tilt increasing to +X should displace the PSF to -X" assert np.allclose(((cen[1] - (n - 1) / 2) * u.pixel * wave.pixelscale).to_value(u.arcsec), tilt_angle), "PSF offset did not match expected amount"
def test_CompoundOpticalSystem_hybrid(npix=128): """ Test that the CompoundOpticalSystem container works for hybrid Fresnel+Fraunhofer systems Defining "works correctly" here is a bit arbitrary given the different physical assumptions. For the purpose of this test we consider a VERY simple case, mostly a Fresnel system. We split out the first optic and put that in a Fraunhofer system. We then test that a compound hybrid system yields the same results as the original fully-Fresnel system. Parameters ---------- npix : int Number of pixels for the pupil sampling. Kept small by default to reduce test run time. """ import poppy opt1 = poppy.SquareAperture() opt2 = poppy.CircularAperture(radius=0.55) ###### Simple test case to exercise the conversion functions, with only trivial propagation osys1 = poppy.OpticalSystem() osys1.add_pupil(opt1) osys2 = poppy.FresnelOpticalSystem() osys2.add_optic(poppy.ScalarTransmission()) osys3 = poppy.OpticalSystem() osys3.add_pupil(poppy.ScalarTransmission()) osys3.add_detector(fov_pixels=64, pixelscale=0.01) cosys = poppy.CompoundOpticalSystem([osys1, osys2, osys3]) psf, ints = cosys.calc_psf(return_intermediates=True) assert len(ints) == 4, "Unexpected number of intermediate wavefronts" assert isinstance(ints[0], poppy.Wavefront), "Unexpected output type" assert isinstance(ints[1], poppy.FresnelWavefront), "Unexpected output type" assert isinstance(ints[2], poppy.Wavefront), "Unexpected output type" ###### Harder case involving more complex actual propagations #===== a single Fresnel optical system ===== osys = poppy.FresnelOpticalSystem(beam_ratio=0.25, npix=128, pupil_diameter=2 * u.m) osys.add_optic(opt1) osys.add_optic(opt2, distance=10 * u.cm) osys.add_optic(poppy.QuadraticLens(1.0 * u.m)) osys.add_optic(poppy.Detector(pixelscale=0.125 * u.micron / u.pixel, fov_pixels=512), distance=1 * u.m) #===== two systems, joined into a CompoundOpticalSystem ===== # First part is Fraunhofer then second is Fresnel osys1 = poppy.OpticalSystem(npix=128, oversample=4, name="FIRST PART, FRAUNHOFER") # Note for strict consistency we need to apply a half pixel shift to optics in the Fraunhofer part; # this accomodates the differences between different types of image centering. pixscl = osys.input_wavefront().pixelscale halfpixshift = (pixscl * 0.5 * u.pixel).to(u.m).value opt1shifted = poppy.SquareAperture(shift_x=halfpixshift, shift_y=halfpixshift) osys1.add_pupil(opt1shifted) osys2 = poppy.FresnelOpticalSystem(name='SECOND PART, FRESNEL') osys2.add_optic(opt2, distance=10 * u.cm) osys2.add_optic(poppy.QuadraticLens(1.0 * u.m)) osys2.add_optic(poppy.Detector(pixelscale=0.125 * u.micron / u.pixel, fov_pixels=512), distance=1 * u.m) cosys = poppy.CompoundOpticalSystem([osys1, osys2]) #===== PSF calculations ===== psf_simple = osys.calc_psf(return_intermediates=False) poppy.poppy_core._log.info( "******=========calculation divider============******") psf_compound = cosys.calc_psf(return_intermediates=False) np.testing.assert_allclose( psf_simple[0].data, psf_compound[0].data, err_msg= "PSFs do not match between equivalent simple and compound/hybrid optical systems" )