def test_compound_osys_errors(): """ Test that it rejects incompatible inputs""" import poppy inputs_and_errors = (( None, "Missing required optsyslist argument" ), ([], "The provided optsyslist argument is an empty list"), ([ poppy.CircularAperture() ], "All items in the optical system list must be OpticalSystem instances" )) for test_input, expected_error in inputs_and_errors: with pytest.raises(ValueError) as excinfo: poppy.CompoundOpticalSystem(test_input) assert _exception_message_starts_with(excinfo, expected_error) osys = poppy.OpticalSystem() osys.add_pupil(poppy.CircularAperture()) cosys = poppy.CompoundOpticalSystem([osys]) with pytest.raises(RuntimeError) as excinfo: cosys.add_pupil(poppy.SquareAperture()) assert _exception_message_starts_with( excinfo, "Adding individual optical elements is disallowed")
def test_ShackHartmannWFS(): """ Test Shack Hartmann Wavefront Sensor class functionality. Show that spot centroid measurement changes when optical system reflects off a deformed mirror """ wavelength = 635 * u.nm # define nominal shack harttmann wavefront sensor: shwfs = sub_sampled_optics.ShackHartmannWavefrontSensor() dm_size = shwfs.lenslet_pitch * 24 # setup flat wavefront and calculate nominal spot locations on SHWFS wf_flat = poppy.Wavefront( diam=dm_size, wavelength=wavelength, npix=int((shwfs.lenslet_pitch / shwfs.pixel_pitch).value * shwfs.n_lenslets * 2)) wf_flat *= poppy.CircularAperture(radius=dm_size / 2) # sample flat wavefront: shwfs.sample_wf(wf_flat) shwfs.get_psfs() flat_centroid_list = shwfs.get_centroids() ## define DM act_x = 2 act_y = 2 stroke = .3e-6 dm_actuator_pitch = dm_size / 4 dm = poppy.dms.ContinuousDeformableMirror( dm_shape=(4, 4), actuator_spacing=dm_actuator_pitch, radius=dm_size / 2, include_factor_of_two=True) dm.set_actuator(act_x, act_y, stroke) # define Wavefront object for simulation, reflect off DM wf = poppy.Wavefront(diam=dm_size, wavelength=wavelength, npix=int( (shwfs.lenslet_pitch / shwfs.pixel_pitch).value * shwfs.n_lenslets * 2)) wf *= poppy.CircularAperture(radius=dm_size / 2) wf *= dm #sample actual wf and propagate to detector: shwfs.sample_wf(wf) shwfs.get_psfs() # reconstruct wavefront and ensure that it is nonzero after reflecting off of deformed DM reconstruction = shwfs.reconstruct_wavefront(flat_centroid_list).value assert np.count_nonzero( reconstruction ) > 0, "Wavefront reconstruction was not non-zero as expected for input DM actuation" return np.count_nonzero(reconstruction) > 0
def test_MatrixFT_FFT_Lyot_propagation_equivalence(display=False): """ Using a simple Lyot coronagraph prescription, perform a simple numerical check for consistency between calcPSF result of standard (FFT) propagation and MatrixFTCoronagraph subclass of OpticalSystem.""" D = 2. wavelen = 1e-6 ovsamp = 4 fftcoron_annFPM_osys = poppy.OpticalSystem(oversample=ovsamp) fftcoron_annFPM_osys.addPupil(poppy.CircularAperture(radius=D / 2)) spot = poppy.CircularOcculter(radius=0.4) diaphragm = poppy.InverseTransmission(poppy.CircularOcculter(radius=1.2)) annFPM = poppy.CompoundAnalyticOptic(opticslist=[diaphragm, spot]) fftcoron_annFPM_osys.addImage(annFPM) fftcoron_annFPM_osys.addPupil(poppy.CircularAperture(radius=0.9 * D / 2)) fftcoron_annFPM_osys.addDetector(pixelscale=0.05, fov_arcsec=3.) # Re-cast as MFT coronagraph with annular diaphragm FPM matrixFTcoron_annFPM_osys = poppy.MatrixFTCoronagraph( fftcoron_annFPM_osys, occulter_box=diaphragm.uninverted_optic.radius_inner) annFPM_fft_psf = fftcoron_annFPM_osys.calcPSF(wavelen) annFPM_mft_psf = matrixFTcoron_annFPM_osys.calcPSF(wavelen) diff_img = annFPM_mft_psf[0].data - annFPM_fft_psf[0].data abs_diff_img = np.abs(diff_img) if display: plt.figure(figsize=(16, 3)) plt.subplot(131) poppy.display_PSF(annFPM_fft_psf, vmin=1e-10, vmax=1e-6, title='Annular FPM Lyot coronagraph, FFT') plt.subplot(132) poppy.display_PSF(annFPM_mft_psf, vmin=1e-10, vmax=1e-6, title='Annular FPM Lyot coronagraph, Matrix FT') plt.subplot(133) plt.imshow((annFPM_mft_psf[0].data - annFPM_fft_psf[0].data), cmap='gist_heat') plt.colorbar() plt.title('Difference (MatrixFT - FFT)') plt.show() print("Max of absolute difference: %.10g" % np.max(abs_diff_img)) assert (np.all(abs_diff_img < 2e-7))
def test_CompoundOpticalSystem(): """ Verify basic functionality of concatenating optical systems """ opt1 = poppy.SquareAperture() opt2 = poppy.CircularAperture(radius=0.55) # a single optical system osys = poppy.OpticalSystem() osys.add_pupil(opt1) osys.add_pupil(opt2) osys.add_detector(pixelscale=0.1, fov_pixels=128) # two systems, joined into a CompoundOpticalSystem osys1 = poppy.OpticalSystem() osys1.add_pupil(opt1) osys2 = poppy.OpticalSystem() osys2.add_pupil(opt2) osys2.add_detector(pixelscale=0.1, fov_pixels=128) cosys = poppy.CompoundOpticalSystem([osys1, osys2]) # PSF calculations psf_simple = osys.calc_psf() psf_compound = cosys.calc_psf() np.testing.assert_allclose( psf_simple[0].data, psf_compound[0].data, err_msg= "PSFs do not match between equivalent simple and compound optical systems" ) # check the planes assert len(cosys.planes) == len(osys1.planes) + len(osys2.planes)
def test_OPD_in_waves_for_FITSOpticalElement(): pupil_radius = 1 * u.m pupil = poppy.CircularAperture(radius=pupil_radius) reference_wavelength = 1 * u.um npix = 16 single_wave_1um_lens = poppy.ThinLens( name='Defocus', nwaves=1, reference_wavelength=reference_wavelength, radius=pupil_radius) osys = poppy.OpticalSystem(oversample=1, npix=npix) osys.add_pupil(pupil) osys.add_pupil(single_wave_1um_lens) osys.add_detector(0.01 * u.arcsec / u.pixel, fov_pixels=3) # We have applied 1 wave of defocus at 1 um, so verify the center # has lower flux than at 2 um (it should be the 'hole' of the donut) psf_1um = osys.calc_psf(reference_wavelength) center_pixel_value = psf_1um[0].data[1, 1] psf_2um = osys.calc_psf(2 * reference_wavelength) assert psf_2um[0].data[1, 1] > psf_1um[0].data[1, 1] # Now, use the single_wave_1um_lens optic to make a # wavelength-independent 1 wave defocus lens_as_fits = single_wave_1um_lens.to_fits(what='opd', npix=3 * npix // 2) lens_as_fits[0].header['BUNIT'] = 'radian' lens_as_fits[0].data *= 2 * np.pi / reference_wavelength.to(u.m).value thin_lens_wl_indep = poppy.FITSOpticalElement(opd=lens_as_fits, opdunits='radian') # We expect identical peak flux for all wavelengths, so check at 0.5x and 2x for prefactor in (0.5, 1.0, 2.0): osys = poppy.OpticalSystem(oversample=1, npix=npix) osys.add_pupil(pupil) osys.add_pupil(thin_lens_wl_indep) osys.add_detector(prefactor * 0.01 * u.arcsec / u.pixel, fov_pixels=3) psf = osys.calc_psf(wavelength=prefactor * u.um) assert np.isclose(center_pixel_value, psf[0].data[1, 1])
def test_measure_radius_at_ee(): """ Test the function measure_radius_at_ee in poppy/utils.py which measures the encircled energy vs radius and return as an interpolator. """ # Tests on a circular aperture o = poppy.OpticalSystem() o.add_pupil(poppy.CircularAperture()) o.add_detector(0.010, fov_pixels=512) psf = o.calc_psf() # Create outputs of the 2 inverse functions rad = utils.measure_radius_at_ee(psf) ee = utils.measure_ee(psf) # The ee and rad functions should undo each other and yield the input value for i in [0.1, 0.5, 0.8]: np.testing.assert_almost_equal(i, ee(rad(i)), decimal=3, err_msg="Error: Values not equal") # Repeat test with normalization to psf sum=1. # This time we can go right up to 1.0, or at least arbitrarilyclose to it. rad = utils.measure_radius_at_ee(psf, normalize='total') ee = utils.measure_ee(psf, normalize='total') for i in [0.1, 0.5, 0.9999]: np.testing.assert_almost_equal(i, ee(rad(i)), decimal=3, err_msg="Error: Values not equal")
def __init__(self): # CLEARP pupil info from: # MODIFIED CALIBRATION OPTIC HOLDER - NIRISS # DRAWING NO 196847 REV 0 COMDEV # Design file name 196847Rev0.pdf sent by Loic Albert # Properties: # 39 mm outer diam, corresponds to the circumscribing pupil of JWST # 2.0 mm vane width # 6.0 mm radius for central obstruction # Note the circumscribing pupil of JWST is 6603.464 mm in diameter # (Ball SER on geometric optics model: BALL-JWST-SYST-05-003) pupil_mag = 6.603464 / 39.0 poppy.CompoundAnalyticOptic.__init__( self, ( poppy.SecondaryObscuration( secondary_radius=6.0 * pupil_mag, support_width=2.0 * pupil_mag, n_supports=3, support_angle_offset=90 + 180), # align first support with +V2 axis # but invert to match OTE exit pupil poppy.CircularAperture(radius=39 * pupil_mag / 2)), name='CLEARP')
def generate_psf(coeffs): coeffs = np.insert(coeffs, 0, 0) # Declare physical constants radius = 2e-3 wavelength = 1500e-9 FOV_pixels = 512 #Increase this (finer) 1st. h = 60e-6 / 2 #Increase this (wider) 2nd f = 4.5e-3 * 2 theta = np.arctan(h / f) / np.pi * 180 * 60 * 60 # in arcsec pixscale = theta / FOV_pixels #somehow resize this after - bilinear reduction of resolution coeffs = (np.asarray(coeffs) / 2) * 1e-6 # Create PSF osys = poppy.OpticalSystem() circular_aperture = poppy.CircularAperture(radius=radius) osys.add_pupil(circular_aperture) thinlens = poppy.ZernikeWFE(radius=radius, coefficients=coeffs) osys.add_pupil(thinlens) osys.add_detector(pixelscale=pixscale, fov_pixels=FOV_pixels) psf_with_zernikewfe, all_wfs = osys.calc_psf(wavelength=wavelength, display_intermediates=False, return_intermediates=True) pupil_wf = all_wfs[1] #this one *** final_wf = all_wfs[-1] #sometimes referred to as wf # psf = psf_with_zernikewfe[0].data return pupil_wf.phase, final_wf.amplitude**2
def test_FITSOpticalElement(tmpdir): circ_fits = poppy.CircularAperture().to_fits(grid_size=3, npix=10) fn = str(tmpdir / "circle.fits") circ_fits.writeto(fn, overwrite=True) # Test passing aperture via file on disk foe = poppy.FITSOpticalElement(transmission=fn) assert foe.amplitude_file == fn assert np.allclose(foe.amplitude, circ_fits[0].data) # Test passing OPD via FITS object, along with unit conversion circ_fits[0].header['BUNIT'] = 'micron' # need unit for OPD foe = poppy.FITSOpticalElement(opd=circ_fits) assert foe.opd_file == 'supplied as fits.HDUList object' assert np.allclose(foe.opd, circ_fits[0].data * 1e-6) # make a cube rect_mask = poppy.RectangleAperture().sample(grid_size=3, npix=10) circ_mask = circ_fits[0].data circ_fits[0].data = np.stack([circ_mask, rect_mask]) circ_fits[0].header['BUNIT'] = 'nm' # need unit for OPD fn2 = str(tmpdir / "cube.fits") circ_fits.writeto(fn2, overwrite=True) # Test passing OPD as cube, with slice default, units of nanometers foe = poppy.FITSOpticalElement(opd=fn2) assert foe.opd_file == fn2 assert foe.opd_slice == 0 assert np.allclose(foe.opd, circ_mask * 1e-9) # Same cube but now we ask for the next slice foe = poppy.FITSOpticalElement(opd=(fn2, 1)) assert foe.opd_file == fn2 assert foe.opd_slice == 1 assert np.allclose(foe.opd, rect_mask * 1e-9)
def test_shifting_optics( npix=30, grid_size = 3, display=False): """Test shifting (translation) of Analytic and FITS Optical elements. Does shifting work as expected? Is it consistent between the two classes? Tests the fix for #247 """ import poppy pixsize =grid_size/npix shift_size = np.round(0.2/pixsize)*pixsize # by construction, an integer # of pixels # Create a reference array circ = poppy.CircularAperture() circ_samp = circ.sample(npix=npix, grid_size=grid_size) # Create a shifted version, and make sure it's different circ_shift = poppy.CircularAperture( shift_x=shift_size) circ_shift_samp = circ_shift.sample(npix=npix, grid_size=grid_size) if display: plt.imshow(circ_samp-circ_shift_samp) assert np.allclose(circ_samp, circ_shift_samp) is False, "Shift didn't change array" # Make a FITS element. circ_fits = circ.to_fits(npix=npix, grid_size=grid_size) # Show we can shift that and get the same result as shifting the analytic element fits_shifted = poppy.FITSOpticalElement(transmission=circ_fits, shift_x=shift_size) np.testing.assert_allclose(fits_shifted.amplitude, circ_shift_samp, atol=1e-9, err_msg="Shifting Analytic and FITS versions are not consistent (v1, via shift_x)") # FITSOpticalElement also lets you specify shifts via fraction of the array. Let's # show that is consistent. This is older syntax that is discouraged, and may be # deprecated and removed eventually. But while available it should be correct. array_frac = shift_size/grid_size fits_shifted_v2 = poppy.FITSOpticalElement(transmission=circ_fits, shift=(array_frac, 0)) np.testing.assert_allclose(fits_shifted.amplitude, fits_shifted_v2.amplitude, atol=1e-9, err_msg="Shifting FITS via shift/shift_x are not consistent") np.testing.assert_allclose(fits_shifted.amplitude, circ_shift_samp, atol=1e-9, err_msg="Shifting Analytic and FITS versions are not consistent (v2, via shift)") # Check in a 1D cut that the amount of shift is as expected - # this is implicitly also checked above via the match of Analytic and FITS # which use totally different methods to perform the shift. shift_in_pixels = int(shift_size/pixsize) assert np.allclose(np.roll(circ_samp[npix//2], shift_in_pixels), circ_shift_samp[npix//2])
def makePSF(self, makePSFInputDict: dict, makePSFOptions: zernikeoptions): coeffs = makePSFInputDict["coeffs"] show = makePSFOptions["show"] units = makePSFOptions["units"] extraPlots = makePSFOptions["extraPlots"] if units is "microns": coeffs = np.asarray(coeffs) * 1e-6 osys = poppy.OpticalSystem() circular_aperture = poppy.CircularAperture(radius=self.radius) osys.add_pupil(circular_aperture) thinlens = poppy.ZernikeWFE(radius=self.radius, coefficients=coeffs) osys.add_pupil(thinlens) # osys.add_detector(pixelscale=self.pixscale, fov_arcsec=self.FOV) osys.add_detector(pixelscale=self.pixscale, fov_pixels=self.FOV_pixels) if extraPlots: plt.figure(1) # psf_with_zernikewfe, final_wf = osys.calc_psf(wavelength=self.wavelength, display_intermediates=show, # return_final=True) psf_with_zernikewfe, all_wfs = osys.calc_psf( wavelength=self.wavelength, display_intermediates=show, return_intermediates=True, ) final_wf = all_wfs[-1] pupil_wf = all_wfs[1] if extraPlots: psf = psf_with_zernikewfe psfImage = psf[0].data # plt.figure(2) # plt.clf() # poppy.display_psf(psf, normalize='peak', cmap='viridis', scale='linear', vmin=0, vmax=1) plt.figure(3) wf = final_wf wf = pupil_wf plt.clf() plt.pause(0.001) plt.subplot(1, 2, 1) plt.imshow(wf.amplitude**2) plt.title("Amplitude ^2") plt.colorbar() plt.subplot(1, 2, 2) plt.imshow(wf.phase) plt.title("Phase") plt.colorbar() plt.tight_layout() poppy.display_psf(psf_with_zernikewfe, title="PSF") self.psf = psf_with_zernikewfe self.wf = final_wf self.complex_psf = self.wf.amplitude * np.exp(1j * self.wf.phase) self.osys_obj = osys self.pupil_wf = pupil_wf
def makeZernikePSF(self, coeffs=(0, 0, 0, 0, 0), show=False, units='microns', extraPlots=False): # RADIUS = 1.0 # meters # WAVELENGTH = 1500e-9 # meters # PIXSCALE = 0.01 # arcsec / pix # FOV = 1 # arcsec # NWAVES = 1.0 # FOV_PIXELS = 128 if units == 'microns': coeffs = np.asarray(coeffs) * 1e-6 osys = poppy.OpticalSystem() circular_aperture = poppy.CircularAperture(radius=self.radius) osys.add_pupil(circular_aperture) thinlens = poppy.ZernikeWFE(radius=self.radius, coefficients=coeffs) osys.add_pupil(thinlens) #osys.add_detector(pixelscale=self.pixscale, fov_arcsec=self.FOV) osys.add_detector(pixelscale=self.pixscale, fov_pixels=self.FOV_pixels) if extraPlots: plt.figure(1) # psf_with_zernikewfe, final_wf = osys.calc_psf(wavelength=self.wavelength, display_intermediates=show, # return_final=True) psf_with_zernikewfe, all_wfs = osys.calc_psf(wavelength=self.wavelength, display_intermediates=show, return_intermediates=True) final_wf = all_wfs[-1] pupil_wf = all_wfs[1] #this one *** if extraPlots: psf = psf_with_zernikewfe psfImage = psf[0].data plt.figure(2) plt.clf() poppy.display_psf(psf, normalize='peak', cmap='viridis', scale='linear', vmin=0, vmax=1) plt.pause(0.001) plt.figure(3) wf = final_wf wf = pupil_wf plt.clf() plt.pause(0.001) plt.subplot(1, 2, 1) plt.imshow(wf.amplitude ** 2) plt.title('Amplitude ^2') plt.colorbar() plt.subplot(1, 2, 2) plt.imshow(wf.phase) plt.title('Phase') plt.colorbar() plt.tight_layout() self.psf = psf_with_zernikewfe self.wf = final_wf self.osys_obj = osys self.pupil_wf = pupil_wf
def test_radial_profile_of_offset_source(): """Test that we can compute the radial profile for a source slightly outside the FOV, compare that to a calculation for a centered source, and check we get consistent results for the overlapping range of the radius parameter space. Also, make a plot showing the consistency. """ import matplotlib.pyplot as plt osys = poppy.OpticalSystem() osys.add_pupil(poppy.CircularAperture(radius=1.0)) osys.add_detector(pixelscale=0.01, fov_pixels=128) # compute centered PSF psf0 = osys.calc_psf() # Compute a PSF with the source offset osys.source_offset_r = 1.0 # outside of FOV psf1 = osys.calc_psf() # Calculate the radial profiles of those two PSFs r0, p0 = poppy.radial_profile(psf0) # For the offset PSF, compute apparent coordinates of the offset source in that image # (this will be a 'virtual' pixel value outside of the FOV) halfsize = psf1[0].header['NAXIS1'] // 2 offset_ypos_in_pixels = osys.source_offset_r / psf1[0].header[ 'PIXELSCL'] + halfsize offset_target_center_pixels = (halfsize, offset_ypos_in_pixels) r1, p1 = poppy.radial_profile(psf1, center=offset_target_center_pixels) fig, axes = plt.subplots(figsize=(16, 5), ncols=3) poppy.display_psf(psf0, ax=axes[0], title='Centered', colorbar_orientation='horizontal') poppy.display_psf(psf1, ax=axes[1], title='Offset', colorbar_orientation='horizontal') axes[2].semilogy(r0, p0) axes[2].semilogy(r1, p1) # Measure radial profiles as interpolator objects, so we can evaluate them at identical radii prof0 = poppy.measure_radial(psf0) prof1 = poppy.measure_radial(psf1, center=offset_target_center_pixels) # Test consistency of those two radial profiles at various radii within the overlap region test_radii = np.linspace(0.4, 0.8, 7) for rad in test_radii: print(prof0(rad), prof1(rad)) axes[2].axvline(rad, ls=':', color='black') # Check PSF agreement within 10%; # also add an absolute tolerance since relative error can be higher for radii right on the dark Airy nuls assert np.allclose( prof0(rad), prof1(rad), rtol=0.1, atol=5e-8 ), "Disagreement between centered and offset radial profiles"
def test_wavefront_tilt_sign_and_direction_fresnel(plot=False, npix=128): """ Test that tilt with increasing WFE towards the +X direction moves the PSF in the -X direction Fresnel propagation version See also test_core.test_source_offsets_in_OpticalSystem """ # Create a wavefront and apply a tilt wave = poppy.FresnelWavefront(beam_radius=0.5 * u.m, npix=npix, oversample=8) wave *= poppy.CircularAperture(radius=0.5 * u.m) # tilt in arcseconds 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 (Fresnel propagation)", fontweight='bold') wave.display(what='both') focal_length = 1 * u.m wave *= poppy.QuadraticLens(f_lens=focal_length) wave.propagate_fresnel(focal_length) if plot: plt.figure() wave.display(what='both', crosshairs=True, imagecrop=0.00001, scale='log') n = wave.shape[0] nominal_cen = n // 2 # In Fresnel mode, PSFs are centered on a pixel by default # (different from in Fraunhofer mode by half a pixel) cen = poppy.measure_centroid(wave.as_fits()) assert np.allclose( cen[0], nominal_cen), "Tilt in X should not displace the PSF in Y" assert cen[ 1] < nominal_cen, "WFE tilt increasing to +X should displace the PSF to -X" assert np.allclose( ((cen[1] - nominal_cen) * u.pixel * wave.pixelscale).to_value(u.m), ((tilt_angle * u.arcsec).to_value(u.radian) * focal_length).to_value( u.m)), "PSF offset distance did not match expected amount"
def test_displays(): # Right now doesn't check the outputs are as expected in any way # TODO consider doing that? But it's hard given variations in matplotlib version etc # As a result this just tests that the code runs to completion, without any assessment # of the correctness of the output displays. import poppy import matplotlib.pyplot as plt osys = poppy.OpticalSystem() osys.add_pupil(poppy.CircularAperture()) osys.add_detector(fov_pixels=128, pixelscale=0.01 * u.arcsec / u.pixel) # Test optical system display # This implicitly exercises the optical element display paths, too osys.display() # Test PSF calculation with intermediate wavefronts plt.figure() psf = osys.calc_psf(display_intermediates=True) # Test PSF display plt.figure() poppy.display_psf(psf) # Test PSF display with other units too poppy.display_psf(psf, angular_coordinate_unit=u.urad) # Test PSF calculation with intermediate wavefronts and other units plt.figure() psf = osys.calc_psf(display_intermediates=True) osys2 = poppy.OpticalSystem() osys2.add_pupil(poppy.CircularAperture()) osys2.add_detector(fov_pixels=128, pixelscale=0.05 * u.urad / u.pixel) psf2, waves = osys.calc_psf(display_intermediates=True, return_intermediates=True) # Test wavefront display, implicitly including other units waves[-1].display() plt.close('all')
def test_inwave_fresnel(plot=False): '''Verify basic functionality of the inwave kwarg for a basic FresnelOpticalSystem()''' npix = 128 oversample = 2 # HST example - Following example in PROPER Manual V2.0 page 49. lambda_m = 0.5e-6 * u.m diam = 2.4 * u.m fl_pri = 5.52085 * u.m d_pri_sec = 4.907028205 * u.m fl_sec = -0.6790325 * u.m d_sec_to_focus = 6.3919974 * u.m m1 = poppy.QuadraticLens(fl_pri, name='Primary') m2 = poppy.QuadraticLens(fl_sec, name='Secondary') hst = poppy.FresnelOpticalSystem(pupil_diameter=diam, npix=npix, beam_ratio=1 / oversample) hst.add_optic(poppy.CircularAperture(radius=diam.value / 2)) hst.add_optic( poppy.SecondaryObscuration(secondary_radius=0.396, support_width=0.0264, support_angle_offset=45.0)) hst.add_optic(m1) hst.add_optic(m2, distance=d_pri_sec) hst.add_optic(poppy.ScalarTransmission( planetype=poppy_core.PlaneType.image, name='focus'), distance=d_sec_to_focus) if plot: plt.figure(figsize=(12, 8)) psf1, wfs1 = hst.calc_psf(wavelength=lambda_m, display_intermediates=plot, return_intermediates=True) # now test the system by inputting a wavefront first wfin = poppy.FresnelWavefront(beam_radius=diam / 2, wavelength=lambda_m, npix=npix, oversample=oversample) if plot: plt.figure(figsize=(12, 8)) psf2, wfs2 = hst.calc_psf(wavelength=lambda_m, display_intermediates=plot, return_intermediates=True, inwave=wfin) wf = wfs1[-1].wavefront wf_no_in = wfs2[-1].wavefront assert np.allclose( wf, wf_no_in ), 'Results differ unexpectedly when using inwave argument for FresnelOpticalSystem().'
def test_grad(): osys = poppy.OpticalSystem() osys.add_pupil(poppy.CircularAperture(radius=3)) # pupil radius in meters osys.add_detector(pixelscale=0.025, fov_arcsec=0.75) # image plane coordinates in arcseconds def objective(wavelength): psf, intermediate = osys.propagate_mono(wavelength * 1e-6) return (np.sum(psf.amplitude**2.)) thisgrad = grad(objective) output = thisgrad(2.0) assert (output - -0.01927825) < 0.0000001 print('Gradient worked!')
def test_propagate(): osys = poppy.OpticalSystem() osys.add_pupil(poppy.CircularAperture(radius=3)) # pupil radius in meters osys.add_detector(pixelscale=0.025, fov_arcsec=0.75) # image plane coordinates in arcseconds def objective(wavelength): psf, intermediate = osys.propagate_mono(wavelength * 1e-6) return (np.sum(psf.amplitude**2.)) output = objective(2.) assert (output - 0.96685994) < 0.00001 print('Propagation worked!')
def make_hawki_poppy_psf(): import poppy osys = poppy.OpticalSystem() m1 = poppy.CircularAperture(radius=4.1) spiders = poppy.SecondaryObscuration(secondary_radius=0.6, n_supports=4, support_width=0.1) vlt = poppy.CompoundAnalyticOptic(opticslist=[m1, spiders], name='VLT') psfs = [] seeing = 0.4 see_psf = simcado.psf.seeing_psf(fwhm=seeing, size=384, pix_res=0.106 / 2) for lam in [1.2, 1.6, 2.2]: osys = poppy.OpticalSystem() osys.add_pupil(vlt) osys.add_detector(pixelscale=0.106, fov_arcsec=0.106 * 192) diff_psf = osys.calc_psf(lam * 1e-6) tmp = deepcopy(diff_psf) tmp[0].data = fftconvolve(diff_psf[0].data, see_psf[0].data, mode="same") tmp[0].data /= np.sum(tmp[0].data) tmp[0].header["SEEING"] = seeing tmp[0].header["CDELT1"] = tmp[0].header["PIXELSCL"] tmp[0].header["CDELT2"] = tmp[0].header["PIXELSCL"] psfs += tmp hdus = fits.HDUList(psfs) for i in range(1, len(hdus)): hdus[i] = fits.ImageHDU(data=hdus[i].data, header=hdus[i].header) hdus.writeto("HAWK-I_config/PSF_HAWKI_poppy.fits", clobber=True) fname = "HAWK-I_config/PSF_HAWKI_poppy.fits" plt.figure(figsize=(15, 4)) for i in range(3): plt.subplot(1, 3, i + 1) poppy.display_PSF(fname, ext=i, title="HAWKI PSF lambda=" + str(fits.getheader(fname, ext=i)["WAVELEN"])) plt.show()
def __init__(self, lenslet_pitch=300 * u.um, lenslet_fl=14.2 * u.mm, pixel_pitch=2.2 * u.um, n_lenslets=12, circular=False, detector=None, **kwargs): self.lenslet_pitch = lenslet_pitch self.lenslet_fl = lenslet_fl self.pixel_pitch = pixel_pitch self.r_lenslet = self.lenslet_pitch / 2. self.n_lenslets = n_lenslets if circular: aperture = poppy.CircularAperture(radius=self.lenslet_pitch / 2, planetype=PlaneType.pupil) else: ap_keywords = { "size": self.lenslet_pitch, "planetype": PlaneType.pupil } aperture = poppy.SquareAperture(size=self.lenslet_pitch, planetype=PlaneType.pupil) optic_array = np.array([[aperture, aperture], [aperture, aperture]]) if detector is None: pixelscale = 1.0 * u.rad / (lenslet_fl * u.pix / pixel_pitch) pix_per_lenslet = int(lenslet_pitch / pixel_pitch) detector = Detector(pixelscale, fov_pixels=pix_per_lenslet) # expand the array to make big_optic_array if n_lenslets % 2 != 0: raise ValueError( "aperture replication only works for even numbers of apertures" ) big_optic_array = optic_array.repeat(n_lenslets / 2., axis=0).repeat(n_lenslets / 2., axis=1) Subapertures.__init__(self, optic_array=big_optic_array, detector=detector, **kwargs) return
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 get_magaox_pupil(npix, rotation=38.75, grid_size=6.5, sm=False): pupil_diam = 6.5 #m secondary = 0.293 * pupil_diam primary = poppy.CircularAperture(radius=pupil_diam/2.) sec = poppy.AsymmetricSecondaryObscuration(secondary_radius=secondary/2., #support_angle=(45, 135, 225, 315), #support_width=[0.01905,]*4, #support_offset_y=[0, -0.34, 0.34, 0], rotation=rotation, name='Complex secondary') opticslist = [primary,] if sm: opticslist.append(sec) pupil = poppy.CompoundAnalyticOptic( opticslist=opticslist, name='Magellan') sampled = pupil.sample(npix=npix, grid_size=grid_size) norm = np.sum(sampled) return sampled
def test_displays(): # Right now doesn't check the outputs are as expected in any way # TODO consider doing that? But it's hard given variations in matplotlib version etc import poppy import matplotlib.pyplot as plt osys = poppy.OpticalSystem() osys.add_pupil(poppy.CircularAperture()) osys.add_detector(fov_pixels=128, pixelscale=0.01) osys.display() plt.figure() psf = osys.calc_psf(display_intermediates=True) plt.figure() #psf = osys.calc_psf(display_intermediates=True) poppy.display_psf(psf)
def __init__(self, name='Gemini South Primary', undersized=False): outer = poppy.CircularAperture(radius=self.primary_diameter/2) outer.pupil_diam = 8.0 # slightly oversized array # Secondary obscuration from pupil diagram provided by Gemini sr = self.obscuration_diameter/2 if undersized: sr = 1.02375/2 # SM outer diameter (vs inner hole projected diameter) # FIXME could antialias using same technique as used for apodizer grids obscuration = poppy.AsymmetricSecondaryObscuration( secondary_radius=sr, support_angle=self.support_angles, support_width=self.support_widths, support_offset_y=self.support_offset_y) return super(GeminiPrimary,self).__init__(opticslist=[outer,obscuration], name=name)
def circular_intensity(wvl, pupil_radius, pixel_scale_par=0.009): """ Calculate and plot circular intensity circular_intensity(wvl, pupil_radius, pixel_scale_par = 0.05) wvl : wavelength in microns """ osys = poppy.OpticalSystem() osys.add_pupil( poppy.CircularAperture(radius=pupil_radius)) # pupil radius in meters planeCor = pupil_radius # This line will let us change coordinates of the plane according to the pupil radius to better represent the diffraction pattern if pupil_radius <= 0.49: planeCor = pupil_radius * 10 osys.add_detector( pixelscale=pixel_scale_par, fov_arcsec=planeCor) # image plane coordinates in arcseconds psf = osys.calc_psf(wvl) # wavelength in meters poppy.display_psf(psf, title='The Circular Aperture')
def test_inwave_fraunhofer(plot=False): '''Verify basic functionality of the inwave kwarg for a basic OpticalSystem()''' npix = 128 oversample = 2 diam = 2.4 * u.m lambda_m = 0.5e-6 * u.m # calculate the Fraunhofer diffraction pattern hst = poppy.OpticalSystem(pupil_diameter=diam, npix=npix, oversample=oversample) hst.add_pupil(poppy.CircularAperture(radius=diam.value / 2)) hst.add_pupil( poppy.SecondaryObscuration(secondary_radius=0.396, support_width=0.0264, support_angle_offset=45.0)) hst.add_image( poppy.ScalarTransmission(planetype=poppy_core.PlaneType.image, name='focus')) if plot: plt.figure(figsize=(9, 3)) psf1, wfs1 = hst.calc_psf(wavelength=lambda_m, display_intermediates=plot, return_intermediates=True) # now test the system by inputting a wavefront first wfin = poppy.Wavefront(wavelength=lambda_m, npix=npix, diam=diam, oversample=oversample) if plot: plt.figure(figsize=(9, 3)) psf2, wfs2 = hst.calc_psf(wavelength=lambda_m, display_intermediates=plot, return_intermediates=True, inwave=wfin) wf = wfs1[-1].wavefront wf_no_in = wfs2[-1].wavefront assert np.allclose( wf, wf_no_in ), 'Results differ unexpectedly when using inwave argument in OpticalSystem().'
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_measure_radius_at_ee(): """ Test the function measure_radius_at_ee in poppy/utils.py which measures the encircled energy vs radius and return as an interpolator. """ # Tests on a circular aperture o = poppy.OpticalSystem() o.add_pupil(poppy.CircularAperture()) o.add_detector(0.010, fov_pixels=512) psf = o.calc_psf() # Create outputs of the 2 inverse functions rad = utils.measure_radius_at_ee(psf) ee = utils.measure_ee(psf) # The ee and rad functions should undo each other and yield the input value for i in [0.1, 0.5, 0.8]: np.testing.assert_almost_equal(i, ee(rad(i)), decimal=3, err_msg="Error: Values not equal")
def test_wavefront_conversions(): """ Test conversions between Wavefront and FresnelWavefront in both directions. """ import poppy props = lambda wf: (wf.shape, wf.ispadded, wf.oversample, wf.pixelscale) optic = poppy.CircularAperture() w = poppy.Wavefront(diam=4 * u.m) w *= optic fw = poppy.FresnelWavefront(beam_radius=2 * u.m) fw *= optic # test convert from Fraunhofer to Fresnel fw2 = poppy.FresnelWavefront.from_wavefront(w) assert props(fw) == props(fw2) #np.testing.assert_allclose(fw.wavefront, fw2.wavefront) # test convert from Fresnel to Fraunhofer w2 = poppy.Wavefront.from_fresnel_wavefront(fw) assert props(w) == props(w2)
wf_error_budget = [] * n_coeff print('Select desired coefficient distribution (default Sigmoid):') print('Sigmoid: 1 \nGaussian: 2 \nExponential: 3 \n') distribution = input() if distribution == 2: wf_error_budget = gaussian_budget(n_coeff) elif distribution == 3: wf_error_budget = exponential_budget(n_coeff) else: wf_error_budget = sigmoid_budget(n_coeff) #%% --------------------------------- OPTICAL ELEMENTS ---------------------------------------------- aperture = poppy.CircularAperture(radius=radius, name='Pupil') objective = poppy.QuadraticLens(fl_obj, name='Objective lens') # ----------------------------------- OPTICAL SYSTEM ------------------------------------------------ # Initialize results as lists psf_results = [] zernike_coeff = [] file_h5, group_h5 = init_h5(save_dir) for image_idx in range(n_set): osys = poppy.FresnelOpticalSystem(pupil_diameter=10 * radius, npix=256,