def test_rotation_in_OpticalSystem(display=False, npix=1024): """ Test that rotation planes within an OpticalSystem work as expected to rotate the wavefront. We can get equivalent results by rotating an Optic a given amount counterclockwise, or rotating the wavefront in the same direction. """ angles_and_tolerances = ((90, 1e-8), (45, 3e-7)) for angle, atol in angles_and_tolerances: osys = poppy.OpticalSystem(npix=npix) osys.add_pupil(poppy.optics.ParityTestAperture(rotation=angle)) osys.add_detector(fov_pixels=128, pixelscale=0.01) psf1 = osys.calc_psf() osys2 = poppy.OpticalSystem(npix=npix) osys2.add_pupil(poppy.optics.ParityTestAperture()) osys2.add_rotation(angle=angle) # note, same sign here. osys2.add_detector(fov_pixels=128, pixelscale=0.01) psf2 = osys2.calc_psf() if display: fig, axes = plt.subplots(figsize=(16, 5), ncols=2) poppy.display_psf(psf1, ax=axes[0]) axes[0].set_title("Optic rotated {} deg".format(angle)) poppy.display_psf(psf2, ax=axes[1]) axes[1].set_title("Wavefront rotated {} deg".format(angle)) assert np.allclose(psf1[0].data, psf2[0].data, atol=atol), ( "PSFs did not agree " f"within the requested tolerance, for angle={angle}." f"Max |difference| = {np.max(np.abs(psf1[0].data - psf2[0].data))}" )
def test_rotation_in_OpticalSystem(display=False, npix=1024): """ Test that rotation planes within an OpticalSystem work as expected to rotate the wavefront. We can get equivalent results by rotating an Optic a given amount, or rotating the wavefront in the opposite direction. """ angles_and_tolerances = ((90, 1e-8), (45, 3e-7)) for angle, atol in angles_and_tolerances: osys = poppy.OpticalSystem(npix=npix) osys.add_pupil(poppy.optics.ParityTestAperture(rotation=angle)) osys.add_detector(fov_pixels=128, pixelscale=0.01) if display: plt.figure() psf1 = osys.calc_psf(display=display) if display: plt.title("Optic rotated {} deg".format(angle)) osys = poppy.OpticalSystem(npix=npix) osys.add_pupil(poppy.optics.ParityTestAperture()) osys.add_rotation(angle=-angle) # note, opposite sign here. osys.add_detector(fov_pixels=128, pixelscale=0.01) if display: plt.figure() psf2 = osys.calc_psf(display=display) if display: plt.title("Wavefront rotated {} deg".format(angle)) assert np.allclose(psf1[0].data, psf2[0].data, atol=atol), ("PSFs did not agree " "within the requested tolerance")
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 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 display_multiHex(rings_number_mother, sec_rad_mother, side_dist_mother = 1.0,segment_gap_mother = 0.01, pixel_scale = 0.010, fov_arcsec_mag = 2.0, figure_size = (12 ,8),wvl = 1e-6): def multi_hexagon(rings_number, sec_rad, side_dist = 1.0, segment_gap = 0.01): """ multi_hexagon(rings_number, sec_rad, side_dist = 1.0, segment_gap = 0.01) # rings : The number of rings of hexagons to include, not counting the central segment # side_dist : Distance between sides (flat-to-flat) of the hexagon, in meters. Default is 1.0 # segment_gap : Gap between adjacent segments, in meters. Default is 0.01 m = 1 cm # sec_rad : scondary obstacle radius """ ap = poppy.MultiHexagonAperture(name='ApertureHex', flattoflat = side_dist, gap = segment_gap,rings =rings_number) # 3 rings of 2 m segments yields 14.1 m circumscribed diameter sec = poppy.SecondaryObscuration(secondary_radius = float(sec_rad), n_supports = 4, support_width = 0.1) # secondary with spiders atlast = poppy.CompoundAnalyticOptic( opticslist=[ap, sec], name='Mock ATLAST') # combine into one optic plt.figure(figsize=(12,8)) atlast.display(npix=1024, colorbar_orientation='vertical') return atlast osys = poppy.OpticalSystem() osys.add_pupil(multi_hexagon(rings_number_mother,sec_rad_mother,side_dist_mother,segment_gap_mother)) osys.add_detector(pixelscale=pixel_scale, fov_arcsec=fov_arcsec_mag) psf = osys.calc_psf(wvl) plt.figure(figsize=figure_size) poppy.display_psf(psf, title="Diffraction Pattern")
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 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_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 rectangle_intensity(rot=0, wd=2, ht=1, xshi=0, yshi=0): """Calculate light intensity for light going through a rectangular slit rectangle_intensity(rot = 0, wd= 2,ht = 1, xshi = 0,yshi = 0) rot : rotation in degrees wd : width in m ht : height in m xshi : x axis shift in m yshi : y axis shift in m """ ap = poppy.RectangleAperture(rotation=rot, width=wd, height=ht, shift_x=xshi, shift_y=yshi) ap.display(colorbar=False) osys = poppy.OpticalSystem() osys.add_pupil(ap) osys.add_detector(pixelscale=0.05, fov_arcsec=2.0) psf = osys.calc_psf(1e-6) plt.figure(figsize=(12, 8)) poppy.display_psf(psf, title="Diffraction Pattern")
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_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 make_image_corners_only(poppy_optical_system,npix,wavs,subimage_pix=256): """ Make an image of the sidelobes only. This is done by simulating 4 detector offsets. Unfortunately it didn't speed up the calculation significantly compared to doing the whole image. """ # Get the TolimanAperture object from the optical system object for p in poppy_optical_system.planes: if isinstance(p,TolimanAperture): aperture = p elif isinstance(p,poppy.Detector): detector = p pixelscale = detector.pixelscale.value image = np.zeros((npix,npix)) # Where are the sidelobes? wmin = np.min(wavs) wmax = np.max(wavs) ideal_offset_pix = diffraction_spot_offset((wmax+wmin)/2,aperture,pixelscale) poppy_optical_system = poppy.OpticalSystem() poppy_optical_system.add_pupil(aperture) poppy_optical_system.add_detector(pixelscale=pixelscale, fov_arcsec=subimage_pix*pixelscale/2*u.arcsec) # Loop through the 4 quadrants and calculate them, then add it to the full image for offset_angle in [45,135,225,315]: for j, wavelength in enumerate(wavs): ideal_offset_pix = diffraction_spot_offset(wavelength,aperture,pixelscale) # Snap it to a grid ideal_offset_x_pix = ideal_offset_pix*np.sign(np.sin(offset_angle*np.pi/180.)) ideal_offset_y_pix = ideal_offset_pix*np.sign(np.cos(offset_angle*np.pi/180.)) offset_x_pix = np.int(np.round(ideal_offset_x_pix)) offset_y_pix = np.int(np.round(ideal_offset_y_pix)) actual_offset_angle = np.arctan2(offset_y_pix,offset_x_pix)*180./np.pi # should be == offset_angle... actual_offset_r_pix = np.sqrt(offset_x_pix**2+offset_y_pix**2) poppy_optical_system.source_offset_theta = -offset_angle poppy_optical_system.source_offset_r = 0.5*actual_offset_r_pix*pixelscale#*u.arcsec corner_im = poppy_optical_system.calc_psf(wavelength)[0].data # Add it to the full image x1 = npix//2-offset_x_pix-subimage_pix//2 x2 = npix//2-offset_x_pix+subimage_pix//2 y1 = npix//2-offset_y_pix-subimage_pix//2 y2 = npix//2-offset_y_pix+subimage_pix//2 image[y1:y2,x1:x2] += corner_im return image
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_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 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_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 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_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_segment_tilt_sign_and_direction(display=False): hexdm = poppy.HexSegmentedDeformableMirror(flattoflat=0.5 * u.m, rings=1) osys2 = poppy.OpticalSystem(pupil_diameter=2 * u.m, npix=128, oversample=1) osys2.add_pupil( poppy.MultiHexagonAperture(flattoflat=0.5 * u.m, rings=1, center=True)) osys2.add_pupil(hexdm) osys2.add_detector(0.10, fov_arcsec=10) psf_ref = osys2.calc_psf() # reference, with no tilts hexdm.set_actuator(0, 0.2 * u.micron, 0, 0) # piston hexdm.set_actuator(1, 0, 2 * u.arcsec, 0) # tip hexdm.set_actuator(2, 0, 0, 1 * u.arcsec) # tilt if display: import matplotlib.pyplot as plt hexdm.display(what='opd', colorbar_orientation='vertical', opd_vmax=2e-6) plt.figure(figsize=(14, 5)) plt.suptitle("Segment tilt sign test (Fraunhofer propagation)", fontweight='bold') psf2 = osys2.calc_psf(display_intermediates=display) diff_psf = psf2[0].data - psf_ref[0].data if display: plt.figure() poppy.display_psf_difference(psf2, psf_ref) assert brighter_left_half( diff_psf ), 'Tilting a segment with +X tilt WFE should move its PSF to -X' assert not brighter_top_half( diff_psf ), 'Tilting a segment with +Y tilt WFE should move its PSF to -Y'
def create_image(self, coefficient_set_init=None, input_noise=None): # This is the function that creates the image (will probably call the config file) from some zernike coefficients # Copy paste the code that creates the image here import poppy from astropy.io import fits pupil_diameter = 6.559 # (in meter) As used in WebbPSF pupil_radius = pupil_diameter / 2 osys = poppy.OpticalSystem() transmission = '/Users/mygouf/Python/webbpsf/webbpsf-data4/jwst_pupil_RevW_npix1024.fits.gz' #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data/NIRCam/OPD/OPD_RevV_nircam_115.fits' opd = '/Users/mygouf/Python/webbpsf/webbpsf-data4/NIRCam/OPD/OPD_RevW_ote_for_NIRCam_requirements.fits.gz' hdul = fits.open(opd) hdul2 = fits.open(transmission) # Create wavefront map #print(coefficient_set_init) zernike_coefficients = np.append(0, coefficient_set_init) #zernike_coefficients *= 1e6 # conversion from meters to microns #wavefront_map = poppy.ZernikeWFE(radius=pupil_radius, # coefficients=zernike_coefficients, # aperture_stop=False) #print(zernike_coefficients) wavefront_map = poppy.zernike.opd_from_zernikes( zernike_coefficients, npix=1024, basis=poppy.zernike.zernike_basis_faster) wavefront_map = np.nan_to_num(wavefront_map) * hdul2[0].data fits.writeto('wavefront_map.fits', wavefront_map, hdul[0].header, overwrite=True) #opd = wavefront_map opd = 'wavefront_map.fits' #myoptic = poppy.FITSOpticalElement(transmission='transfile.fits', opd='opdfile.fits', pupilscale="PIXELSCL") #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data4/NIRCam/OPD/OPD_RevW_ote_for_NIRCam_requirements.fits.gz' jwst_opd = poppy.FITSOpticalElement(transmission=transmission, opd=opd) #jwst_opd = poppy.FITSOpticalElement(transmission=transmission) osys.add_pupil(jwst_opd) # JWST pupil osys.add_detector( pixelscale=0.063, fov_arcsec=self.fov_arcsec, oversample=4) # image plane coordinates in arcseconds psf = osys.calc_psf(4.44e-6) # wavelength in microns psf_poppy = np.array(psf[0].data) poppy.display_psf(psf, title='JWST NIRCam test') #psf1 = osys.calc_psf(4.44e-6) # wavelength in microns #psf2 = osys.calc_psf(2.50e-6) # wavelength in microns #psf_poppy = psf1[0].data/2 + psf2[0].data/2 psf_poppy = psf_poppy * 1e7 / np.max(psf_poppy) # Adding photon noise #image = np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy)) if np.all(input_noise) == None: #noise = np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>1000)) #noise = self.rs.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>np.max(psf_poppy)/1000)) noise = np.random.normal( loc=psf_poppy, scale=np.sqrt(psf_poppy > np.max(psf_poppy) / 1000)) #noise = rs.poisson(psf_poppy) #print('Estimated Noise', np.mean(noise)) else: noise = input_noise #print('Input Noise', np.mean(noise)) image = psf_poppy + noise dict_ = { 'image': image, 'noise': noise, 'wavefront_map': wavefront_map } #print(np.mean(image),np.mean(noise)) return dict_
def test_radial_profile(plot=False): """ Test radial profile calculation, including circular and square apertures, and including with the pa_range option. """ ### Tests on a circular aperture o = poppy_core.OpticalSystem() o.add_pupil(poppy.CircularAperture(radius=1.0)) o.add_detector(0.010, fov_pixels=512) psf = o.calc_psf() rad, prof = poppy.radial_profile(psf) rad2, prof2 = poppy.radial_profile(psf, pa_range=[-20, 20]) rad3, prof3 = poppy.radial_profile(psf, pa_range=[-20 + 90, 20 + 90]) # Compute analytical Airy function, on exact same radial sampling as that profile. v = np.pi * rad * poppy.misc._ARCSECtoRAD * 2.0 / 1e-06 airy = ((2 * scipy.special.jn(1, v)) / v)**2 r0 = 33 # 0.33 arcsec ~ first airy ring in this case. airy_peak_envelope = airy[r0] * prof.max() / (rad / rad[r0])**3 absdiff = np.abs(prof - airy * prof.max()) if plot: import matplotlib.pyplot as plt plt.figure(figsize=(12, 6)) plt.subplot(1, 2, 1) poppy.display_psf(psf, colorbar_orientation='horizontal', title='Circular Aperture, d=2 m') plt.subplot(1, 2, 2) plt.semilogy(rad, prof) plt.semilogy(rad2, prof2, ls='--', color='red') plt.semilogy(rad3, prof3, ls=':', color='cyan') plt.semilogy(rad, airy_peak_envelope, color='gray') plt.semilogy(rad, airy_peak_envelope / 50, color='gray', alpha=0.5) plt.semilogy(rad, absdiff, color='purple') # Test the radial profile is close to the analytical Airy function. # It's hard to define relative fractional closeness for comparisons to # a function with many zero crossings; we can't just take (f1-f2)/(f1+f2) # This is a bit of a hack but let's test that the difference between # numerical and analytical is always less than 1/50th of the peaks of the # Airy function (fit based on the 1/r^3 power law fall off) assert np.all(absdiff[0:300] < airy_peak_envelope[0:300] / 50) # Test that the partial radial profiles agree with the full one. This test is # a little tricky since the sampling in r may not agree exactly. # TODO write test comparison here # Let's also test that the partial radial profiles on 90 degrees agree with each other. # These should match to machine precision. assert np.allclose(prof2, prof3) ### Next test is on a square aperture o = poppy.OpticalSystem() o.add_pupil(poppy.SquareAperture()) o.add_detector(0.010, fov_pixels=512) psf = o.calc_psf() rad, prof = poppy.radial_profile(psf) rad2, prof2 = poppy.radial_profile(psf, pa_range=[-20, 20]) rad3, prof3 = poppy.radial_profile(psf, pa_range=[-20 + 90, 20 + 90]) if plot: plt.figure(figsize=(12, 6)) plt.subplot(1, 2, 1) poppy.display_psf(psf, colorbar_orientation='horizontal', title='Square Aperture, size=1 m') plt.subplot(1, 2, 2) plt.semilogy(rad, prof) plt.semilogy(rad2, prof2, ls='--', color='red') plt.semilogy(rad3, prof3, ls=':', color='cyan') assert np.allclose(prof2, prof3)
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" )
def test_measure_FWHM(display=False, verbose=False): """ Test the utils.measure_FWHM function Current implementation can be off by a couple percent for small FWHMs that are only marginally well sampled by the array pixels, so the allowed tolerance for measured_fwhm = input_fwhm is that it's allowed to be off by a couple percent. """ # Test the basic output on simple Gaussian arrays desired = (3, 4.5, 5, 8, 12) tolerance = 0.01 for des in desired: desired_fwhm = des #4.0 # pixels pxscl = 0.010 center = (24.5, 26.25) ar = makeGaussian(50, fwhm=desired_fwhm, center=center) testfits = fits.HDUList(fits.PrimaryHDU(ar)) testfits[0].header['PIXELSCL'] = pxscl meas_fwhm = utils.measure_fwhm(testfits, center=center) if verbose: print("Measured FWHM: {0:.4f} arcsec, {1:.4f} pixels ".format( meas_fwhm, meas_fwhm / pxscl)) reldiff = np.abs((meas_fwhm / pxscl) - desired_fwhm) / desired_fwhm result = "Measured: {3:.4f} pixels; Desired: {0:.4f} pixels. Relative difference: {1:.4f} Tolerance: {2:.4f}".format( desired_fwhm, reldiff, tolerance, meas_fwhm / pxscl) if verbose: print(result) assert reldiff < tolerance, result # Test on Poppy outputs too # We test both well sampled and barely sampled cases. # In this test case the FWHM is 0.206265 arcsec, so pixel scale up to 0.2 arcsec. pixscales = [0.01, 0.1, 0.2] # We allow slightly worse accurance for less well sampled data tolerances = [0.01, 0.015, 0.04] for pixscale, tolerance in zip(pixscales, tolerances): import astropy.units as u o = poppy.OpticalSystem() o.add_pupil(poppy.CircularAperture(radius=0.5 * u.m)) o.add_detector(pixscale, fov_pixels=128) psf = o.calc_psf(wavelength=1 * u.micron) meas_fwhm = poppy.measure_fwhm(psf) expected_fwhm = ((1 * u.micron / (1 * u.m)).decompose().value * u.radian).to(u.arcsec).value reldiff = np.abs((meas_fwhm - expected_fwhm) / expected_fwhm) result = "Measured: {3:.4f} arcsec; Desired: {0:.4f} arcsec. Relative difference: {1:.4f} Tolerance: {2:.4f}".format( expected_fwhm, reldiff, tolerance, meas_fwhm) assert reldiff < tolerance, result
def _get_optical_system(self, fft_oversample=2, detector_oversample=None, fov_arcsec=2.8, fov_pixels=None, options=dict()): """ Return an OpticalSystem instance corresponding to the instrument as currently configured. When creating such an OpticalSystem, you must specify the parameters needed to define the desired sampling, specifically the oversampling and field of view. Parameters ---------- fft_oversample : int Oversampling factor for intermediate plane calculations. Default is 2 detector_oversample: int, optional By default the detector oversampling is equal to the intermediate calculation oversampling. If you wish to use a different value for the detector, set this parameter. Note that if you just want images at detector pixel resolution you will achieve higher fidelity by still using some oversampling (i.e. *not* setting `oversample_detector=1`) and instead rebinning down the oversampled data. fov_pixels : float Field of view in pixels. Overrides fov_arcsec if both set. fov_arcsec : float Field of view, in arcseconds. Default is 2 options : dict Other arbitrary options for optical system creation Returns ------- osys : poppy.OpticalSystem an optical system instance representing the desired configuration. """ if detector_oversample is None: detector_oversample = fft_oversample #poppy_core._log.debug("Oversample: %d %d " % (fft_oversample, detector_oversample)) optsys = poppy.OpticalSystem(name=self.name, oversample=fft_oversample) if 'source_offset_r' in options.keys(): optsys.source_offset_r = options['source_offset_r'] if 'source_offset_theta' in options.keys(): optsys.source_offset_theta = options['source_offset_theta'] optsys.npix = self.npix #---- set pupil intensity pupil_optic = GeminiPrimary(undersized=self._undersized_secondary) #if self._undersized_secondary: #pupil_optic.obscuration_diameter = 1.02375 # SM outer diameter (vs inner hole projected diameter) #---- set pupil OPD if isinstance(self.pupilopd, str): # simple filename full_opd_path = self.pupilopd if os.path.exists( self.pupilopd) else os.path.join(self._datapath, "OPD", self.pupilopd) elif hasattr(self.pupilopd, '__getitem__') and isinstance( self.pupilopd[0], basestring): # tuple with filename and slice full_opd_path = (self.pupilopd[0] if os.path.exists( self.pupilopd[0]) else os.path.join( self._datapath, "OPD", self.pupilopd[0]), self.pupilopd[1]) elif isinstance(self.pupilopd, fits.HDUList): # OPD supplied as FITS HDUList object full_opd_path = self.pupilopd # not a path per se but this works correctly to pass it to poppy elif self.pupilopd is None: full_opd_path = None else: raise TypeError( "Not sure what to do with a pupilopd of that type:" + str(type(self.pupilopd))) #---- apply pupil intensity and OPD to the optical model optsys.add_pupil(name='Gemini Primary', optic=pupil_optic, opd=full_opd_path, opdunits='micron', rotation=self._rotation) if self.dms: optsys.add_pupil(optic=self.woofer) optsys.add_pupil(optic=self.tweeter) # GPI Apodizer apod = GPI_Apodizer(name=self.apodizer, satspots=self.satspots) optsys.add_pupil(optic=apod) if self._display_before: optsys.add_image(optic=poppy.ScalarTransmission(name='Before FPM', transmission=1)) # GPI FPM fpm = GPI_FPM(name=self.occulter) optsys.add_image(optic=fpm) if self._display_before: optsys.add_pupil(optic=poppy.ScalarTransmission(name='Before Lyot', transmission=1)) # GPI Lyot Mask lyot = GPI_LyotMask(name=self.lyotmask, tabs=self.lyot_tabs) optsys.add_pupil(optic=lyot) #--- add the detector element. if fov_pixels is None: fov_pixels = np.round(fov_arcsec / self.pixelscale) if 'parity' in self.options.keys(): if self.options['parity'].lower() == 'odd' and np.remainder( fov_pixels, 2) == 0: fov_pixels += 1 if self.options['parity'].lower() == 'even' and np.remainder( fov_pixels, 2) == 1: fov_pixels += 1 optsys.add_detector(self.pixelscale, fov_pixels=fov_pixels, oversample=detector_oversample, name=self.name + " lenslet array") return optsys
def test_source_offsets_in_OpticalSystem(npix=128, fov_size=1, verbose=False): """Test source offsets within the field move in the expected directions and by the expected amounts The source offset positions are specified in the *output* detector coordinate frame, (i.e. for where the PSF should appear in the output image), but we create the wavefront in the entrance pupil coordinate frame. These may be different if there are coordinate transforms for axes flips or rotations. Therefore test several cases and ensure the output PSF appears in the expected location in each case. Parameters: ---------- npix : int number of pixels fov_size : fov size in arcsec (pretty much arbitrary) """ if npix < 110: raise ValueError( "npix < 110 results in too few pixels for fwcentroid to work properly." ) pixscale = fov_size / npix center_coords = np.asarray((npix - 1, npix - 1)) / 2 ref_psf1 = None # below we will save and compare PSFs with transforms to one without. for transform in ['no', 'inversion', 'rotation', 'both']: osys = poppy.OpticalSystem(oversample=1, npix=npix) osys.add_pupil(poppy.CircularAperture(radius=1.0)) if transform == 'inversion' or transform == 'both': if verbose: print("ADD INVERSION") osys.add_inversion(axis='y') if transform == 'rotation' or transform == 'both': if verbose: print("ADD ROTATION") osys.add_rotation(angle=12.5) osys.add_detector(pixelscale=pixscale, fov_pixels=npix) # a PSF with no offset should be centered psf0 = osys.calc_psf() cen = poppy.measure_centroid(psf0) assert np.allclose( cen, center_coords), "PSF with no source offset should be centered" if verbose: print( f"PSF with no offset OK for system with {transform} transform.\n" ) # Compute a PSF with the source offset towards PA=0 (+Y), still within the FOV osys.source_offset_r = 0.3 * fov_size # Shift to PA=0 should move in +Y osys.source_offset_theta = 0 psf1 = osys.calc_psf() cen = poppy.measure_centroid(psf1) assert np.allclose( (cen[0] - center_coords[0]) * pixscale, osys.source_offset_r, rtol=0.1), "Measured centroid in Y did not match expected offset" assert np.allclose( cen[1], center_coords[1], rtol=0.1 ), "Measured centroid in X should not shift for this test case" if verbose: print( f"PSF with +Y offset OK for system with {transform} transform.\n" ) if ref_psf1 is None: ref_psf1 = psf1 else: assert np.allclose( ref_psf1[0].data, psf1[0].data, atol=1e-4 ), "PSF is inconsistent with the system without any transforms" # Shift to PA=90 should move in -X osys.source_offset_theta = 90 psf2 = osys.calc_psf() cen = poppy.measure_centroid(psf2) assert np.allclose( (cen[1] - center_coords[1]) * pixscale, -osys.source_offset_r, rtol=0.1), "Measured centroid in X did not match expected offset" assert np.allclose( cen[0], center_coords[0], rtol=0.1 ), "Measured centroid in Y should not shift for this test case" if verbose: print( f"PSF with -X offset OK for system with {transform} transform.\n" )
def create_image_from_opd_file(self, opd=None, input_noise=None): # This is the function that creates the image (will probably call the config file) from some zernike coefficients # Copy paste the code that creates the image here import poppy from astropy.io import fits pupil_diameter = 6.559 # (in meter) As used in WebbPSF pupil_radius = pupil_diameter / 2 osys = poppy.OpticalSystem() transmission = '/Users/mygouf/Python/webbpsf/webbpsf-data4/jwst_pupil_RevW_npix1024.fits.gz' #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data/NIRCam/OPD/OPD_RevV_nircam_115.fits' #opd = '/Users/mygouf/Python/webbpsf/webbpsf-data4/NIRCam/OPD/OPD_RevW_ote_for_NIRCam_requirements.fits.gz' hdul = fits.open(opd) hdul2 = fits.open(transmission) wavefront_map = hdul[0].data jwst_opd = poppy.FITSOpticalElement(transmission=transmission, opd=opd) #jwst_opd = poppy.FITSOpticalElement(transmission=transmission) osys.add_pupil(jwst_opd) # JWST pupil osys.add_detector( pixelscale=0.063, fov_arcsec=self.fov_arcsec, oversample=4) # image plane coordinates in arcseconds psf = osys.calc_psf(4.44e-6) # wavelength in microns psf_poppy = np.array(psf[0].data) poppy.display_psf(psf, title='JWST NIRCam test') #psf1 = osys.calc_psf(4.44e-6) # wavelength in microns #psf2 = osys.calc_psf(2.50e-6) # wavelength in microns #psf_poppy = psf1[0].data/2 + psf2[0].data/2 psf_poppy = psf_poppy * 1e7 / np.max(psf_poppy) # Adding photon noise #image = np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy)) if np.all(input_noise) == None: #noise = np.random.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>1000)) #noise = self.rs.normal(loc=psf_poppy, scale=np.sqrt(psf_poppy>np.max(psf_poppy)/1000)) noise = np.random.normal( loc=psf_poppy, scale=np.sqrt(psf_poppy > np.max(psf_poppy) / 1000)) #noise = rs.poisson(psf_poppy) #print('Estimated Noise', np.mean(noise)) else: noise = input_noise #print('Input Noise', np.mean(noise)) image = psf_poppy + noise dict_ = { 'image': image, 'noise': noise, 'wavefront_map': wavefront_map } #print(np.mean(image),np.mean(noise)) return dict_