import numpy as np import sys sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics') from fourierOptics import * ''' Author: Joost van den Born, PhD student at University of Groningen Contact: born@astro.rug.nl Description: Script to analyse the shift of the PSF centroid as a function of the RMS value of the WFE. Higher wavefront errors will result in a larger shift of the centroid. Script made for the SPIE manuscript. ''' if __name__ == '__main__': plt.rc('font', family='serif') plt.rc('text', usetex=True) plt.rc('axes', labelsize='large') # Setting some input parameters dpix = 160 n, m = 1001, 1001 l = 1.49e-3 # wavelength zern_rms = 25e-5 # RMS of the zernike component of the WFE midspat_rms = 25e-5 # RMS of the PSD component of the WFE rmsList = 10**np.linspace(-6, -3, 25) # 1 to 1000 nm WFE RMS in mm, equidistant in log-space input_field_location = './data/ADC_input_field_1001x1001_1.49_onaxis_obscured.pkl' # Initiate ELT-MICADO and wavefront error objects mcd = ParaxialMICADO(l=l, n=n, m=m, dpix=dpix, entrance_pupil='open') wfe = WavefrontError(n,m) print ('Your resolution is {}x{} with a pixel size of {}'.format(n,m,dpix)) # Do the unperturbed propagation for a reference PSF E, x, y = mcd.propagate(l=l,surface=-1, zoom=False, show=False, print_progress=False, hx=0, hy=0, load_up_to_adc=input_field_location) # Generate a PSD surface psd_surface = wfe.generate_height_map(midspat_rms, 2, 1) # Fit the PSD surface using zernike terms # Generating a normalized grid for the Zernike evaluation yz,xz = np.mgrid[-1:1:wfe.n*1j, -1:1:wfe.m*1j] _r = np.sqrt(xz**2 + yz**2) _mask = _r > 1 coeffic = wfe.fit_zernike_coefficients(psd_surface, modes=66) # StrehlList_zern, StrehlList_psd = [], [] rms_zern_list, rms_psd_list = [], [] # psd lists popt_airy_list_psd, pcov_airy_list_psd = [], [] popt_cust_list_psd, custom_var_list_psd, strehl_list_psd = [], [], [] Exyzlist_psd = [] # zern lists popt_airy_list_zern, pcov_airy_list_zern = [], [] popt_cust_list_zern, custom_var_list_zern, strehl_list_zern = [], [], [] Exyzlist_zern = [] # Now iterate over the wavefront rms values. for rms in rmsList: # Print results print('RMS: {:.3f} nm'.format(rms*1e6)) ######################################## # WFE described by Zernike polynomials # ######################################## # Make a new perturbed wavefront error with the required RMS coeffic = wfe.scale_zernike_coefficients(coeffic, rms) hzern = wfe.zernike_cartesian(coeffic, xz, yz) # Add the new wavefront error mcd.ELT_ADC.p1.add_wavefront_error(hzern) # Performing the propagation using Fresnel approximation to the focal plane. Exyz_zern,xzern,yzern = mcd.propagate(l=l,surface=-1, zoom=False, show=False, print_progress=False, hx=0, hy=0, load_up_to_adc=input_field_location) psfFit_zern = PSFFitter(xzern.real,yzern.real,np.abs(Exyz_zern)**2) Exyzlist_zern.append(Exyz_zern) # Fit using airy pattern amplitude_guess = np.max(np.abs(Exyz_zern))**2 airyFitted, popt_airy, pcov_airy = psfFit_zern.fit_fixed_obscuration_airy_PSF(initialGuess=(amplitude_guess, 0.0, 0.0, 0.0, 0.03)) popt_airy_list_zern.append(popt_airy) pcov_airy_list_zern.append([ np.sqrt(pcov_airy[i][i]) for i in range(len(pcov_airy)) ]) strehl_list_zern.append( np.abs(psfFit_zern.get_coupling(E, Exyz_zern))**2 ) ######################################## ######################################## # WFE described by PSD profile # ######################################## factor = rms / midspat_rms h_psd = wfe.scale_rms(factor, data=psd_surface) # Add the new wavefront error mcd.ELT_ADC.p1.add_wavefront_error(h_psd) # Performing the propagation using Fresnel approximation to the focal plane. Exyz_psd,xpsd,ypsd = mcd.propagate(l=l,surface=-1, zoom=False, show=False, print_progress=False, hx=0, hy=0, load_up_to_adc=input_field_location) psfFit_psd = PSFFitter(xpsd.real,ypsd.real,np.abs(Exyz_psd)**2) # Fit using coupling integral Exyzlist_psd.append(Exyz_psd) # Fit using airy pattern amplitude_guess = np.max(np.abs(Exyz_psd))**2 airyFitted, popt_airy, pcov_airy = psfFit_psd.fit_fixed_obscuration_airy_PSF(initialGuess=(amplitude_guess, 0.0, 0.0, 0.0, 0.03)) popt_airy_list_psd.append(popt_airy) pcov_airy_list_psd.append([ np.sqrt(pcov_airy[i][i]) for i in range(len(pcov_airy)) ]) strehl_list_psd.append( np.abs(psfFit_psd.get_coupling(E, Exyz_psd))**2 ) ######################################## # Find the RMS value of the wavefront errros rms_zern = np.sqrt(np.mean(np.asarray(hzern[~_mask])**2)) rms_psd = np.sqrt(np.mean(np.asarray(h_psd)**2)) rms_zern_list.append(rms_zern) rms_psd_list.append(rms_psd) print('Strehl: {:.3f} ... {:.3f}'.format(strehl_list_psd[-1], strehl_list_zern[-1])) # PSD # Optimum fit parameters for all scaled surface errors _, x0_airy_psd, y0_airy_psd, _, _ = np.asarray(popt_airy_list_psd).T # Errors in the optimum fit parameters for all scaled surface errors _, x0_airy_psd_err, y0_airy_psd_err, _, _ = np.asarray(pcov_airy_list_psd).T # Distance from original centroid at (0,0) r_airy_psd = np.sqrt(x0_airy_psd**2+y0_airy_psd**2) r_err_psd_airy = np.sqrt( (x0_airy_psd**2*x0_airy_psd_err**2+y0_airy_psd**2*y0_airy_psd_err**2) / (x0_airy_psd**2+y0_airy_psd**2)) rms_psd_nm = np.asarray(rms_psd_list)*1e6 r_airy_psd_mas = r_airy_psd * mcd.platescale * 1000 r_err_psd_airy_mas = r_err_psd_airy * mcd.platescale * 1000 # Zernikes # Optimum fit parameters for all scaled surface errors _, x0_airy_zern, y0_airy_zern, _, _ = np.asarray(popt_airy_list_zern).T # Errors in the optimum fit parameters for all scaled surface errors _, x0_airy_zern_err, y0_airy_zern_err, _, _ = np.asarray(pcov_airy_list_zern).T # Distance from original centroid at (0,0) r_airy_zern = np.sqrt(x0_airy_zern**2+y0_airy_zern**2) r_err_zern_airy = np.sqrt( (x0_airy_zern**2*x0_airy_zern_err**2+y0_airy_zern**2*y0_airy_zern_err**2) / (x0_airy_zern**2+y0_airy_zern**2)) rms_zern_nm = np.asarray(rms_zern_list)*1e6 r_airy_zern_mas = r_airy_zern * mcd.platescale * 1000 r_err_zern_airy_mas = r_err_zern_airy * mcd.platescale * 1000 save_variable('./data/centroid_shift_zernikes.pkl', (rms_zern_nm, r_airy_zern_mas, r_err_zern_airy_mas, strehl_list_zern)) save_variable('./data/centroid_shift_psd.pkl', (rms_psd_nm, r_airy_psd_mas, r_err_psd_airy_mas, strehl_list_psd)) # Plot some of the point spread functions fig = plt.figure(figsize=(9,3)) ax1 = fig.add_subplot(131) ax1.imshow(np.abs(Exyzlist_zern[0][450:550,450:550]), cmap='hot', interpolation='bicubic') ax1.set_xticks([]) ax1.set_yticks([]) ax2 = fig.add_subplot(132) ax2.imshow(np.abs(Exyzlist_zern[-5][450:550,450:550]), cmap='hot', interpolation='bicubic') ax2.set_xticks([]) ax2.set_yticks([]) ax4 = fig.add_subplot(133) ax4.imshow(np.abs(Exyzlist_psd[-5][450:550,450:550]), cmap='hot', interpolation='bicubic') ax4.set_xticks([]) ax4.set_yticks([]) plt.tight_layout() plt.savefig('./figures/PSF_shape.png', bbox_inches='tight', transparent=True) plt.savefig('./figures/PSF_shape.pdf', bbox_inches='tight', transparent=True) plt.show()