Files
fourierOptics/scripts/SPIE/centroid_shift_analysis.py

190 lines
7.2 KiB
Python

import numpy as np
import sys
sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Documents/Published/FourierOptics GitHub release/')
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()