Added off-axis propagation, SPIE scripts

This commit is contained in:
yosata committed 2020-11-02 17:16:33 +01:00
1 parent 61ca707689
commit 04eb56d529
20 files changed
+2068 -39

No files matched your search

+190
View File
@@ -0,0 +1,190 @@
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()
+94
View File
@@ -0,0 +1,94 @@
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 generate two plots of the footprint on MCD-CAM lens,
showing the impact of the WFEs on the amplitude and phase
near the exit pupil.
Script made for the SPIE manuscript.
'''
if __name__ == '__main__':
plt.rc('text', usetex=True)
plt.rc('font', family='serif')
# Initializing MICADO paraxial model
mcd = ParaxialMICADO(n=3001, m=3001, dpix=50)
wfe = WavefrontError(mcd.n, mcd.m)
# Setup
l = 1.49e-3
# Set field points
hxList = [-1,-1,0, 1,1]
hyList = [-1, 1,0,-1,1]
# Propagate through the field positions without any WFEs
for i in range(len(hxList)):
hx = hxList[i]
hy = hyList[i]
print('hx: {} - hy: {}'.format(hx,hy) )
# Propagate through the MICADO system
prop_surf = 4
E, x, y = mcd.propagate(l=l,surface=prop_surf, zoom=False, show=False,
print_progress=False, hx=hx, hy=hy)
# Save the resulting 'perfect' PSF for later
if prop_surf == 4:
# Don't show anything outside the clear aperture.
mask = np.sqrt(x**2 + y**2) > mcd.ELT_ADC.p1.CA/2
# Plot P1
footprint_p1 = E.copy()
footprint_p1[mask]=np.nan
# figp1, axp1, axp2 = plot_results(footprint_p1[850:2150,850:2150], x[850:2150,850:2150], y[850:2150,850:2150],
# fig_size=(12,5), show=False)
figp1, axp1 = plot_real_valued_results(np.abs(footprint_p1)[850:2150,850:2150], x[850:2150,850:2150], y[850:2150,850:2150],
labeltext='Amplitude', show=False, fig_size=None)
plt.savefig('./figures/ELT-MCD-ADC_footprint_no_wfe_{}_{}.pdf'.format(hx,hy), bbox_inches='tight', transparent=True)
plt.savefig('./figures/ELT-MCD-ADC_footprint_no_wfe_{}_{}.png'.format(hx,hy), bbox_inches='tight', transparent=True)
plt.show()
# Load in the WFEs and set them to the corresponding ADC surfaces
wfe_p1 = load_variable('./data/ADC_tWFE_p1_psd_25nm.pkl')
wfe_p2 = load_variable('./data/ADC_tWFE_p2_psd_25nm.pkl')
wfe_p3 = load_variable('./data/ADC_tWFE_p3_psd_25nm.pkl')
wfe_p4 = load_variable('./data/ADC_tWFE_p4_psd_25nm.pkl')
mcd.ELT_ADC.p1.add_wavefront_error(wfe_p1)
mcd.ELT_ADC.p2.add_wavefront_error(wfe_p2)
mcd.ELT_ADC.p3.add_wavefront_error(wfe_p3)
mcd.ELT_ADC.p4.add_wavefront_error(wfe_p4)
# Iterating over the field positions.
print('\nPropagation with Wavefront Errors applied')
prop_surf = 4
for i in range(len(hxList)):
hx = hxList[i]
hy = hyList[i]
print('hx: {} - hy: {}'.format(hx,hy) )
# Propagate through the MICADO system
Ep, xp, yp = mcd.propagate(l=l,surface=prop_surf+4, zoom=False, show=False,
print_progress=False, hx=hx, hy=hy)
if prop_surf == 4:
# Don't show anything outside the clear aperture.
mask = np.sqrt(x**2 + y**2) > mcd.ELT_ADC.p1.CA/2
# Plot P1
footprint_p1 = Ep.copy()
figp1, axp1, axp2 = plot_results(footprint_p1[1050:1350,1150:1450], x[1050:1350,1150:1450], y[1050:1350,1150:1450],
fig_size=(12,5), show=False)
plt.savefig('./figures/ELT-MCD-COL_footprint_with_wfe_{}_{}_zoom.pdf'.format(hx,hy), bbox_inches='tight', transparent=True)
plt.savefig('./figures/ELT-MCD-COL_footprint_with_wfe_{}_{}_zoom.png'.format(hx,hy), bbox_inches='tight', transparent=True)
plt.show()
+35
View File
@@ -0,0 +1,35 @@
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 propagate an input field to a surface in the
ELT-MICADO system. Here we propagate to the first ADC
surface and save the result, saving us a lot of time
in later analyses.
Script made for the SPIE manuscript.
'''
if __name__ == '__main__':
plt.rc('font', family='serif')
plt.rc('text', usetex=True)
# Setting some input parameters
dpix = 160
n, m = 1001, 1001
l = 1.49e-3 # wavelength
input_field_location = './data/ADC_input_field_{}x{}_{:.2f}_onaxis_obscured.pkl'.format(n,m,l*1e3)
# Initiate ELT-MICADO and wavefront error objects
mcd = ParaxialMICADO(l=l, n=n, m=m, dpix=dpix, entrance_pupil='obscured')
print ('Your resolution is {}x{} with a pixel size of {}'.format(n,m,dpix))
# Propagation up to the first ADC surface.
E, x, y = mcd.propagate(l=l,surface=3, zoom=False, show=True,
print_progress=True, hx=0, hy=0)
save_variable(input_field_location, (E,x,y))
+21
View File
@@ -0,0 +1,21 @@
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 generate the ray traced plot of the paraxial
ELT-MICADO model.
Script made for the SPIE manuscript.
'''
if __name__ == '__main__':
plt.rc('text', usetex=True)
plt.rc('font', family='serif')
# Initializing MICADO paraxial model
mcd = ParaxialMICADO(l=1.49e-3, n=3001, m=3001, dpix=50)
mcd.plot_ray_trace()
+159
View File
@@ -0,0 +1,159 @@
import numpy as np
import os
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 compare the Strehl ratio of 50 different instances
of a generated WFE. Higher spatial frequency perturbations
should follow the Marechal approximation more closely than
lower order shape errors only. We verify this using Power
Spectral Density and Zernike modes (66 coefficients).
Script made for the SPIE manuscript.
'''
def expected_strehl2(rms, l):
return np.exp( -(2*np.pi*rms/l)**2 )
def append_to_existing_data(A, filename):
if os.path.exists(filename):
A_o = load_variable(filename)
A2 = np.append(A, A_o)
else:
A2 = A
return A2
if __name__ == '__main__':
# 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 = np.linspace(1e-6, 1e-3, 25) # 1 to 1000 nm WFE RMS in mm
number_of_iterations = 50
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))
# Propagate unperturbed field
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)
# Initiate psf fitter object to calculate the strehl ratios
psf = PSFFitter(x, y, E)
# Create some empty lists for later
strehl_zern_complete, strehl_psd_complete = [], []
rms_zern_list_complete, rms_psd_list_complete = [], []
# Repeat the analysis 50 times
for i in range(number_of_iterations):
print('Iteration {}/{}'.format(i+1,number_of_iterations))
# 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 = [], []
# Compare results at different RMS values
for rms in rmsList:
########################################
# 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)
Strehl_zern = np.abs(psf.get_coupling(E, Exyz_zern))**2
StrehlList_zern.append(Strehl_zern)
########################################
########################################
# 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)
Strehl_psd = np.abs(psf.get_coupling(E, Exyz_psd))**2
StrehlList_psd.append(Strehl_psd)
########################################
# 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 results
print('RMS: {:.3f}, S_zern: {:.3f}, S_psd: {:.3f}, \
RMS_zern: {:.3f}, RMS_psd: {:.3f}'.format(
rms*1e6, Strehl_zern, Strehl_psd, rms_zern*1e6, rms_psd*1e6))
# Put the results in the correct list
strehl_zern_complete.append(StrehlList_zern)
strehl_psd_complete.append(StrehlList_psd)
rms_zern_list_complete.append(rms_zern_list)
rms_psd_list_complete.append(rms_psd_list)
rms_zern_list_complete = np.asarray(rms_zern_list_complete)
rms_psd_list_complete = np.asarray(rms_psd_list_complete)
# Import old results if they are available and append them to the results.
rmsList_present = rmsList.copy()
rmsList = append_to_existing_data(rmsList, './data/SR_RMS_values_obscured.pkl')
strehl_zern_complete = append_to_existing_data(strehl_zern_complete, './data/SR_strehl_zern_obscured.pkl')
strehl_psd_complete = append_to_existing_data(strehl_psd_complete, './data/SR_strehl_psd_obscured.pkl')
rms_zern_list_complete = append_to_existing_data(rms_zern_list_complete, './data/SR_rms_zern_complete_obscured.pkl')
rms_psd_list_complete = append_to_existing_data(rms_psd_list_complete, './data/SR_rms_psd_complete_obscured.pkl')
mean_strehl_zern = np.mean(strehl_zern_complete, axis=0)
err_strehl_zern = np.std(strehl_zern_complete, axis=0)
mean_strehl_psd = np.mean(strehl_psd_complete, axis=0)
err_strehl_psd = np.std(strehl_psd_complete, axis=0)
save_variable('./data/SR_RMS_values_obscured.pkl', rmsList)
save_variable('./data/SR_strehl_zern_obscured.pkl', strehl_zern_complete)
save_variable('./data/SR_strehl_psd_obscured.pkl', strehl_psd_complete)
save_variable('./data/SR_rms_zern_complete_obscured.pkl', rms_zern_list_complete)
save_variable('./data/SR_rms_psd_complete_obscured.pkl', rms_psd_list_complete)
+88
View File
@@ -0,0 +1,88 @@
import numpy as np
import sys
sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics')
from fourierOptics import *
from pdb import set_trace
'''
Author: Joost van den Born, PhD student at University of Groningen
Contact: born@astro.rug.nl
Description: Script to generate the wavefront errors that will be used for
the ADC prisms during all subsequent analyses. Script made for
the SPIE manuscript.
'''
def main():
plt.rc('text', usetex=True)
plt.rc('font', family='serif')
# Initializing MICADO paraxial model
l = 1.49e-3
mcd = ParaxialMICADO(n=3001, m=3001, dpix=50)
wfe = WavefrontError(mcd.n, mcd.m)
# Get x3 and y3 (the coordinates to apply the ADC WFE to):
_, x3, y3 = mcd.propagate(l=l,surface=4, zoom=False, show=False,
print_progress=False)
# Apply a set of wavefront errors to each ADC surface.
print('\nSetting the wavefront errors...')
wfe_p1 = ADC_surfaces(x3,y3)
wfe_p2 = ADC_surfaces(x3,y3)
wfe_p3 = ADC_surfaces(x3,y3)
wfe_p4 = ADC_surfaces(x3,y3)
wfe_p1.generate_ADC_wfe(zernRMS=20e-6, psdRMS=15e-6)
wfe_p2.generate_ADC_wfe(zernRMS=20e-6, psdRMS=15e-6)
wfe_p3.generate_ADC_wfe(zernRMS=20e-6, psdRMS=15e-6)
wfe_p4.generate_ADC_wfe(zernRMS=20e-6, psdRMS=15e-6)
mask = np.sqrt(wfe_p1.xz**2 + wfe_p1.yz**2) > 1
# Plot P1
surf_p1 = wfe_p1.latest_surface.copy()
surf_p1[mask]=np.nan
figp1, axp1 = plot_real_valued_results(surf_p1[850:2150,850:2150]*1e6, x3[850:2150,850:2150], y3[850:2150,850:2150],
colormap='jet', labeltext='Surface height (nm)', fig_size=None, show=False)
plt.savefig('./figures/ADC_tWFE_p1_zern_20nm_psd_15nm.pdf', bbox_inches='tight', transparent=True)
plt.savefig('./figures/ADC_tWFE_p1_zern_20nm_psd_15nm.png', bbox_inches='tight', transparent=True)
plt.close()
# Plot P2
surf_p2 = wfe_p2.latest_surface.copy()
surf_p2[mask]=np.nan
figp2, axp2 = plot_real_valued_results(surf_p2[850:2150,850:2150]*1e6, x3[850:2150,850:2150], y3[850:2150,850:2150],
colormap='jet', labeltext='Surface height (nm)', fig_size=None, show=False)
plt.savefig('./figures/ADC_tWFE_p2_zern_20nm_psd_15nm.pdf', bbox_inches='tight', transparent=True)
plt.savefig('./figures/ADC_tWFE_p2_zern_20nm_psd_15nm.png', bbox_inches='tight', transparent=True)
plt.close()
# Plot P3
surf_p3 = wfe_p3.latest_surface.copy()
surf_p3[mask]=np.nan
figp3, axp3 = plot_real_valued_results(surf_p3[850:2150,850:2150]*1e6, x3[850:2150,850:2150], y3[850:2150,850:2150],
colormap='jet', labeltext='Surface height (nm)', fig_size=None, show=False)
plt.savefig('./figures/ADC_tWFE_p3_zern_20nm_psd_15nm.pdf', bbox_inches='tight', transparent=True)
plt.savefig('./figures/ADC_tWFE_p3_zern_20nm_psd_15nm.png', bbox_inches='tight', transparent=True)
plt.close()
# Plot P4
surf_p4 = wfe_p4.latest_surface.copy()
surf_p4[mask]=np.nan
figp4, axp4 = plot_real_valued_results(surf_p4[850:2150,850:2150]*1e6, x3[850:2150,850:2150], y3[850:2150,850:2150],
colormap='jet', labeltext='Surface height (nm)', fig_size=None, show=False)
plt.savefig('./figures/ADC_tWFE_p4_zern_20nm_psd_15nm.pdf', bbox_inches='tight', transparent=True)
plt.savefig('./figures/ADC_tWFE_p4_zern_20nm_psd_15nm.png', bbox_inches='tight', transparent=True)
plt.close()
# Get the wavefront errors and save them for later use.
save_variable('./data/ADC_tWFE_p1_zern_20nm_psd_15nm.pkl', wfe_p1.latest_surface)
save_variable('./data/ADC_tWFE_p2_zern_20nm_psd_15nm.pkl', wfe_p2.latest_surface)
save_variable('./data/ADC_tWFE_p3_zern_20nm_psd_15nm.pkl', wfe_p3.latest_surface)
save_variable('./data/ADC_tWFE_p4_zern_20nm_psd_15nm.pkl', wfe_p4.latest_surface)
if __name__ == '__main__':
main()
+162
View File
@@ -0,0 +1,162 @@
import numpy as np
import sys
# sys.path.insert(0, '/Users/joost/Dropbox/Werk/PhD/Analyses/Python/Analytical/Fourier Optics')
sys.path.insert(0, '/home/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 generate the geometric distortions due to WFEs
off the ADC prisms. The distortions are produced by measuring
the offset of the PSF centroid with and without WFE as a
function of field position.
Script made for the SPIE manuscript.
'''
def plot_distortions():
# Coordinates without WFEs
xe = load_variable('./data/GD_obscured_PSF_xcentroid_expected_zern_psd.pkl')
ye = load_variable('./data/GD_obscured_PSF_ycentroid_expected_zern_psd.pkl')
# Coordinates with WFEs
xf = load_variable('./data/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl')
yf = load_variable('./data/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl')
mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, l=1.49e-3)
n = xe.shape[0]
X = xe.flatten()
Y = ye.flatten()
dX = xf - xe
dY = yf - ye
dx = dX.flatten()
dy = dY.flatten()
# Magnitude of the distortions
R = np.sqrt(dx**2 + dy**2) * mcd.platescale * 1e6
# Dummy figure, used for the colorbar colors in the quiver plot
im = plt.imshow(np.reshape(R, (n,n)))
plt.close()
fig = plt.figure()
ax = fig.add_subplot()
for i in range(len(xe)):
ax.plot(xe[:,i], ye[:,i], 'k-', alpha=0.2, linewidth=.5)
ax.plot(xe[i,:], ye[i,:], 'k-', alpha=0.2, linewidth=.5)
ax.quiver(X,Y,dx,dy, angles='xy', edgecolor='k', facecolor='None', linewidth=.5, units='dots')
ax.quiver(X,Y,dx,dy,R, alpha=.5, units='dots')
ax.set_aspect('equal', adjustable='box')
ax.set_xlabel('x (mm)')
ax.set_ylabel('y (mm)')
fig.colorbar(im, ax=ax, label='PSF centroid shift ($\\mu$as)')
plt.savefig('./figures/GD_full_field_zern_psd.pdf', bbox_inches='tight', transparent=True)
plt.savefig('./figures/GD_full_field_zern_psd.png', bbox_inches='tight', transparent=True)
plt.tight_layout()
plt.show()
if __name__ == '__main__':
# plt.rc('text', usetex=True)
# plt.rc('font', family='serif')
# Setup of simulation
l = 1.49e-3
Elist, Eplist = [], []
xlist, ylist = [], []
xplist, yplist = [], []
x_expected_list, y_expected_list = [], []
x_centroid_list, y_centroid_list = [], []
# Set field points
hxList, hyList = np.mgrid[-1:1:11j,-1:1:11j]-(1/300)
hxList = hxList.flatten()
hyList = hyList.flatten()
# Loading in the WFEs
wfe_p1 = load_variable('./data/ADC_tWFE_p1_zern_20nm_psd_15nm.pkl')
wfe_p2 = load_variable('./data/ADC_tWFE_p2_zern_20nm_psd_15nm.pkl')
wfe_p3 = load_variable('./data/ADC_tWFE_p3_zern_20nm_psd_15nm.pkl')
wfe_p4 = load_variable('./data/ADC_tWFE_p4_zern_20nm_psd_15nm.pkl')
# Propagate through the field positions without any WFEs
for i in range(len(hxList)):
hx = hxList[i]
hy = hyList[i]
print('hx: {} - hy: {}'.format(hx,hy) )
# Initializing MICADO paraxial model
mcd = ParaxialMICADO(n=3001, m=3001, dpix=50, entrance_pupil='obscured')
# Propagate through the MICADO system
E, x, y = mcd.propagate(l=l,surface=-1, zoom=False, show=False,
print_progress=False, hx=hx, hy=hy)
# Find approximation for the centroid and amplitude as a first
# guess for the Obscured Airy pattern fit.
amplitude_guess = np.max(np.abs(E))**2
xbc, ybc, _, _ = mcd.centroid_shift(method='Barycenter')
print('Barycenter guess: ', xbc, ybc)
# Do the Obscured Airy Pattern Fit for the unperturbed PSF
xe, ye, _, _ = mcd.centroid_shift(method='Obscured Airy', initialGuess=(amplitude_guess, xbc, ybc, 0.0, 0.03))
print('Airy result: ', xe, ye)
x_expected_list.append(xe)
y_expected_list.append(ye)
# Load in the WFEs and set them to the corresponding ADC surfaces
print('\nSetting the wavefront errors...')
# null_wfe = np.zeros((3001,3001))
mcd.ELT_ADC.p1.add_wavefront_error(wfe_p1)
mcd.ELT_ADC.p2.add_wavefront_error(wfe_p2)
mcd.ELT_ADC.p3.add_wavefront_error(wfe_p3)
mcd.ELT_ADC.p4.add_wavefront_error(wfe_p4)
# Propagate through the MICADO system
print('Propagating with Wavefront Errors applied...')
Ep, xp, yp = mcd.propagate(l=l,surface=-1, zoom=False, show=False,
print_progress=False, hx=hx, hy=hy)
# Do the Obscured Airy Pattern Fit for the perturbed PSF
xf, yf, _, _ = mcd.centroid_shift(method='Obscured Airy', initialGuess=(amplitude_guess, xe, ye, 0.0, 0.03))
# Resulting centroid shift due to the WFE
x_centroid_list.append(xf)
y_centroid_list.append(yf)
R = np.sqrt( (xf-xe)**2 + (yf-ye)**2 ) * mcd.platescale * 1e6
print('xf: {}, yf: {}, shift (uas): {}'.format(xf,yf, R))
# Clear the memory by emptying the large arrays
mcd = None
wfe = None
E, x, y = None, None, None
Ep, xp, yp = None, None, None
# Reshaping the arrays
shape = (int(np.sqrt(len(x_expected_list))), int(np.sqrt(len(x_expected_list))))
x_expected = np.asarray(x_expected_list).reshape(shape)
y_expected = np.asarray(y_expected_list).reshape(shape)#.T
# Saving the arrays
save_variable('./data/GD_obscured_PSF_xcentroid_expected_zern_psd.pkl', x_expected)
save_variable('./data/GD_obscured_PSF_ycentroid_expected_zern_psd.pkl', y_expected)
# Reshaping the arrays
shape = (int(np.sqrt(len(x_centroid_list))), int(np.sqrt(len(x_centroid_list))))
x_centroid = np.asarray(x_centroid_list).reshape(shape)
y_centroid = np.asarray(y_centroid_list).reshape(shape)
# Saving the arrays
save_variable('./data/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl', x_centroid)
save_variable('./data/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl', y_centroid)
plot_distortions()
+78
View File
@@ -0,0 +1,78 @@
import numpy as np
import os
import sys
import matplotlib.pyplot as plt
import matplotlib.cm as cm
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 plot the results of the centroid shift analysis.
Script made for the SPIE manuscript.
'''
def plot_colourline(x,y,c, ax):
'''
Source: https://stackoverflow.com/questions/36505587/color-line-by-third-variable-python
Function to colour a line according to a thrid variable.
In our case, this will be the Strehl ratio
'''
c = cm.plasma_r(c)
for i in np.arange(len(x)-1):
ax.plot([x[i],x[i+1]], [y[i],y[i+1]], c=c[i])
return
if __name__ == '__main__':
plt.rc('font', family='serif')
plt.rc('text', usetex=True)
# Load in the results from centroid_shift_analysis.py
rms_zern_nm, r_airy_zern_mas, r_err_zern_airy_mas, strehl_list_zern = load_variable('./data/centroid_shift_zernikes.pkl')
rms_psd_nm, r_airy_psd_mas, r_err_psd_airy_mas, strehl_list_psd = load_variable('./data/centroid_shift_psd.pkl')
# Convert the errors to logarithmic space, assuming small errors.
log_err_zern_airy_mas = 0.434 * r_err_zern_airy_mas / r_airy_zern_mas
log_err_psd_airy_mas = 0.434 * r_err_psd_airy_mas / r_airy_psd_mas
# Plot the resulting WFE scaling relation
fig = plt.figure(figsize=(9,7))
ax1 = fig.add_subplot(111)
ax1.errorbar((0.8138)*rms_zern_nm, r_airy_zern_mas, yerr=log_err_zern_airy_mas, color='k',
linestyle='none', capsize=3,
markeredgewidth=1)
ax1.errorbar((0.8138)*rms_psd_nm, r_airy_psd_mas, yerr=log_err_psd_airy_mas, color='k',
linestyle='none', capsize=3,
markeredgewidth=1)
ax1.scatter((0.8138)*rms_zern_nm, r_airy_zern_mas, c=strehl_list_zern, marker='v', label='Zernikes', cmap='plasma_r')
im = ax1.scatter((0.8138)*rms_psd_nm, r_airy_psd_mas, c=strehl_list_psd, marker='o', label='PSD', cmap='plasma_r')
# We colour the lines by their Strehl ratio
plot_colourline((0.8138)*rms_zern_nm, r_airy_zern_mas, strehl_list_zern, ax=ax1)
plot_colourline((0.8138)*rms_psd_nm, r_airy_psd_mas, strehl_list_psd, ax=ax1)
ax1.set_xscale('log')
ax1.set_yscale('log')
ax1.set_xlabel('$\\sigma_{\\mathrm{WFE}}$ (nm)', fontsize=18)
ax1.set_ylabel('Centroid shift (mas)', fontsize=18)
ax1.set_xlim([np.min(rms_zern_nm)*(0.8138), np.max(rms_zern_nm)*(0.8138)])
ax1.legend(fontsize=18)
ax1.grid(True, alpha=0.3, which='major')
ax1.grid(True, alpha=0.1, which='minor')
ax1.tick_params(labelsize=18)
cb = fig.colorbar(im, ax=ax1)
cb.set_label(label='Strehl', size=18)
cb.ax.tick_params(labelsize=18)
plt.savefig('./figures/WFE_scaling_relation_obscured_airy.png', bbox_inches='tight', transparent=True)
plt.savefig('./figures/WFE_scaling_relation_obscured_airy.pdf', bbox_inches='tight', transparent=True)
plt.tight_layout()
plt.show()
@@ -0,0 +1,65 @@
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 plot the results of the geometric distortion analysis.
Script made for the SPIE manuscript.
'''
if __name__ == '__main__':
plt.rc('font', family='serif')
plt.rc('text', usetex=True)
# Full field
xe = load_variable('../SPIE/data/GD full field (Obscured Airy zern psd)/GD_obscured_PSF_xcentroid_expected_zern_psd.pkl')
ye = load_variable('../SPIE/data/GD full field (Obscured Airy zern psd)/GD_obscured_PSF_ycentroid_expected_zern_psd.pkl')
xp = load_variable('../SPIE/data/GD full field (Obscured Airy zern psd)/GD_obscured_PSF_xcentroid_found_with_wfe_airy_zern_psd.pkl')
yp = load_variable('../SPIE/data/GD full field (Obscured Airy zern psd)/GD_obscured_PSF_ycentroid_found_with_wfe_airy_zern_psd.pkl')
# Shape of the arrays
n = xe.shape[0]
# Reshaping of the arrays so that they can be used for the quiver plot.
X = xe.flatten()
Y = ye.flatten()
dX = xp-xe
dY = yp-ye
dx = dX.flatten()
dy = dY.flatten()
# Two values are comparatively (extremely large), they are set to 0
# to be able to see the other values.
R = (np.sqrt(dx**2 + dy**2) * 100.13151872258219 * 1000) # Magnitude of the shift in micro arcseconds
print('RMS of the PSF shifts: {:.4f} uas.'.format(np.sqrt(np.mean((R)**2))))
# Dummy figure
im = plt.imshow(np.reshape(R, (n,n)))
plt.close()
# Actual plotting of the figure
fig = plt.figure(figsize=(9,7))
ax = fig.add_subplot()
for i in range(len(xe)):
ax.plot(xe[:,i], ye[:,i], 'k-', alpha=0.2, linewidth=.5)
ax.plot(xe[i,:], ye[i,:], 'k-', alpha=0.2, linewidth=.5)
ax.quiver(X,Y,dx,dy, angles='xy', edgecolor='k', facecolor='None', linewidth=1)
ax.quiver(X,Y,dx,dy,R, alpha=1)
ax.set_aspect('equal', adjustable='box')
ax.set_xlabel('x (mm)', fontsize=18)
ax.set_ylabel('y (mm)', fontsize=18)
ax.set_xlim([-340,340])
ax.set_ylim([-340,340])
ax.tick_params(labelsize=18)
cb = fig.colorbar(im, ax=ax)
cb.set_label(label='PSF centroid shift ($\\mu$as)', size=18)
cb.ax.tick_params(labelsize=18)
plt.savefig('./figures/GD_full_field_obscured_Airy_psd_zern.pdf', bbox_inches='tight', transparent=True)
plt.savefig('./figures/GD_full_field_obscured_Airy_psd_zern.png', bbox_inches='tight', transparent=True)
plt.tight_layout()
plt.show()
+79
View File
@@ -0,0 +1,79 @@
import numpy as np
import os
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 generate the plots of the relation between the
Strehl ratio and the RMS of the Wavefront Error. A comparison
with the Marechal approximation is made.
Script made for the SPIE manuscript.
'''
def expected_strehl2(rms, l):
return np.exp( -(2*np.pi*rms/l)**2 )
def append_to_existing_data(A, filename):
if os.path.exists(filename):
A_o = load_variable(filename)
A = np.append(A, A_o)
return A
def plot_some_stuff():
fig = plt.figure(figsize=(9,7))
ax = fig.add_subplot(111)
ax2 = ax.twiny()
rmsValues = rmsList[:25]
for i in range(len(rms_zern_list_complete)):
ax.plot((0.8138)*rms_zern_list_complete[i]*1e6, strehl_zern_complete[i], 'y-',
alpha=0.1, linewidth=.5)
ax.plot((0.8138)*rms_psd_list_complete[i]*1e6, strehl_psd_complete[i], 'b-',
alpha=0.1, linewidth=.5)
ax.plot(np.linspace(1e-6,1e-3,100)*1e6, expected_strehl2(np.linspace(1e-6,1e-3,100),l),'k--',
label='$S=\\exp[-(2\\pi\\sigma/\\lambda)^2]$')
ax.errorbar((0.8138)*rmsValues*1e6, mean_strehl_zern, yerr=err_strehl_zern, color='y',
linestyle='-', label='Zernikes', capsize=3, markeredgewidth=1)#,
# linewidth=1)
ax.errorbar((0.8138)*rmsValues*1e6, mean_strehl_psd, yerr=err_strehl_psd, color='b',
linestyle='-', label='PSD', capsize=3, markeredgewidth=1)#,
# linewidth=1)
ax.set_xlabel('$\\sigma_{\\mathrm{WFE}}$ (nm)', fontsize=18)
ax.set_ylabel('Strehl', fontsize=18)
ax.set_ylim([0,1])
ax.set_xlim([np.min(rmsValues)*1e6*(0.8138), np.max(rmsValues)*1e6*(0.8138)])
ax2.set_xlim([np.min(rmsValues*(0.8138)/l), np.max(rmsValues*(0.8138)/l)])
ax2.set_xlabel('$\\sigma/\\lambda$', fontsize=18)
ax.legend(fontsize=18)
ax.grid(True, alpha=0.3)
ax.tick_params(labelsize=18)
ax2.tick_params(labelsize=18)
plt.tight_layout()
plt.savefig('./figures/SR_relation_WFE_on_ADC_p1_obscured.png', bbox_inches='tight', transparent=True)
plt.savefig('./figures/SR_relation_WFE_on_ADC_p1_obscured.pdf', bbox_inches='tight', transparent=True)
plt.show()
if __name__ == '__main__':
plt.rc('font', family='serif')
plt.rc('text', usetex=True)
plt.rc('axes', labelsize='large')
l = 1.49e-3
rmsList = load_variable('./data/SR_RMS_values_obscured.pkl')
strehl_zern_complete = load_variable('./data/SR_strehl_zern_obscured.pkl')
strehl_psd_complete = load_variable('./data/SR_strehl_psd_obscured.pkl')
rms_zern_list_complete = load_variable('./data/SR_rms_zern_complete_obscured.pkl')
rms_psd_list_complete = load_variable('./data/SR_rms_psd_complete_obscured.pkl')
mean_strehl_zern = np.mean(strehl_zern_complete, axis=0)
err_strehl_zern = np.std(strehl_zern_complete, axis=0)
mean_strehl_psd = np.mean(strehl_psd_complete, axis=0)
err_strehl_psd = np.std(strehl_psd_complete, axis=0)
plot_some_stuff()
+97
View File
@@ -0,0 +1,97 @@
import numpy as np
import os
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 plot the results of the analysis where the shift
of the PSF centroid as a function of the RMS value of the
WFE was investigated.
Script made for the SPIE manuscript.
'''
if __name__ == '__main__':
plt.rc('font', family='serif')
plt.rc('text', usetex=True)
plt.rc('axes', labelsize='large')
# Optical system constant
platescale = 100.13151872258219
###############################
### Lowpass filter analysis ###
###############################
# Load in the data
r_airy, r_err_airy = load_variable('./data/lowpass_frequency_filter_airy_distance.pkl')
x0_airy, y0_airy, x0_airy_err, y0_airy_err = load_variable('./data/lowpass_frequency_filter_airy_locations.pkl')
central_frequencies = np.asarray([0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20, 25,
30, 35, 40, 45, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200,
220, 250, 280, 310, 340, 370, 400, 450, 500])
r_airy_mas = r_airy * platescale
r_err_airy_mas = r_err_airy * platescale
# Plotting the centroid shift for all bandpasses
fig = plt.figure(figsize=(9,7))
ax1 = fig.add_subplot(111)
ax1.scatter(central_frequencies, r_airy_mas, edgecolor='k', facecolors='none', alpha=0.7)
ax1.set_xscale('log')
ax1.set_xlabel('Cut-off Frequency of the lowpass filter ($\\rho/\\rho_c$)', fontsize=18)
ax1.set_ylabel('Centroid shift (mas)', fontsize=18)
ax1.tick_params(labelsize=18)
ax1.grid(True, alpha=0.3, which='major')
ax1.grid(True, alpha=0.1, which='minor')
ax1.set_xlim([0.3,550])
plt.tight_layout()
plt.savefig('./figures/wfe_frequency_analysis/lowpass_spatial_freq_distance.png', bbox_inches='tight', transparent=True)
plt.savefig('./figures/wfe_frequency_analysis/lowpass_spatial_freq_distance.pdf', bbox_inches='tight', transparent=True)
################################
### Bandpass filter analysis ###
################################
# Load in the data
r_airy, r_err_airy = load_variable('./data/bandpass_frequency_filter_airy_distance.pkl')
x0_airy, y0_airy, x0_airy_err, y0_airy_err = load_variable('./data/bandpass_frequency_filter_airy_locations.pkl')
central_frequencies = load_variable('./data/bandpass_frequency_filter_central_frequencies.pkl')
print(central_frequencies, len(central_frequencies), len(r_airy))
r_airy_mas = r_airy * platescale
r_err_airy_mas = r_err_airy * platescale
# Fit a line in Logarithmic space
log_cf = np.log10(central_frequencies)
log_r = np.log10(r_airy_mas)
coefficients = np.polyfit(log_cf[2:], log_r[2:], 1)
best_fit_fnc = np.poly1d(coefficients)
best_fit_y = best_fit_fnc(log_cf)
# Plotting the centroid shift for all bandpasses
fig = plt.figure(figsize=(9,7))
ax1 = fig.add_subplot(111)
ax1.scatter(central_frequencies, r_airy_mas, edgecolor='k', facecolors='none', alpha=0.7)
ax1.plot(central_frequencies[2:], 10**best_fit_y[2:], 'b-', alpha=0.5, label='Best fit: $y = 0.030 x^{-0.602}$')
ax1.set_xscale('log')
ax1.set_yscale('log')
ax1.set_xlabel('Central Frequency of the bandpass filter ($\\rho/\\rho_c$)', fontsize=18)
ax1.set_ylabel('Centroid shift (mas)', fontsize=18)
ax1.tick_params(labelsize=18)
ax1.grid(True, alpha=0.3, which='major')
ax1.grid(True, alpha=0.1, which='minor')
ax1.set_xlim([0.3,550])
ax1.set_ylim([10**-6, 10**0])
ax1.legend(fontsize=18)
plt.tight_layout()
plt.savefig('./figures/wfe_frequency_analysis/bandpass_spatial_freq_distance.png', bbox_inches='tight', transparent=True)
plt.savefig('./figures/wfe_frequency_analysis/bandpass_spatial_freq_distance.pdf', bbox_inches='tight', transparent=True)
plt.show()
@@ -0,0 +1,252 @@
import numpy as np
import os
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.
Script made for the SPIE manuscript.
'''
if __name__ == '__main__':
plt.rc('font', family='serif')
plt.rc('axes', labelsize='large')
# Pick a wavelength
l = 1.49e-3
input_field_location = './data/ADC_input_field_1001x1001_1.49_onaxis_circular.pkl'
# Initializing MICADO paraxial model
mcd = ParaxialMICADO(n=1001, m=1001, dpix=160, entrance_pupil='open')
# Propagation of the unperturbed system.
Exyz_og, xp, yp = 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 the wavefront error that will be used.
wfe = WavefrontError(mcd.n,mcd.m)
wfe.generate_height_map(5e-5, 2, 1) # 50 nm RMS for standard, power slope 2, cut-off 1
h_data = wfe.data
# Plotting the WFE, PSD and image planes
oC = [np.min(xp), np.max(xp), np.min(yp), np.max(yp)]
# Mask out areas where the ADC prism isn't
_, xadc, yadc = mcd.propagate(l=l,surface=3, zoom=False, show=False,
print_progress=True, hx=0, hy=0)
mask = np.sqrt(xadc**2 + yadc**2) > 62.5
# Setting up empty lists for fitting process
popt_airy_list, popt_cust_list = [], []
pcov_airy_list, custom_var_list = [], []
Exyzlist, PSDlist, surfacelist = [], [], []
central_frequencies = []
# Bins in LogSpace
x = mcd.x
flist = np.asarray([0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20, 25,
30, 35, 40, 45, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200,
220, 250, 280, 310, 340, 370, 400, 450, 500])
plt.imshow(wfe.data*1e6, cmap='jet');plt.colorbar();plt.show()
###############################
### Lowpass filter analysis ###
###############################
# Do the analysis for each frequency in flist
for fmax in flist:
print('Lowpass filter - Freq.: {} - Sample {:02d}/{:02d}'.format(fmax, flist.tolist().index(fmax) + 1, len(flist) ) )
if fmax == flist[0]:
wfe.set_lowpass_filter(fmax)
else:
i = flist.tolist().index(fmax) - 1
wfe.set_lowpass_filter(flist[i])
scal = wfe.filteredData
# Adding the wavefront error
mcd.ELT_ADC.p1.add_wavefront_error(scal)
# Performing the propagation using Fresnel approximation to the focal plane.
Exyz_psd,xp,yp = mcd.propagate(l=l,surface=-1, zoom=False, show=False,
print_progress=False, hx=0, hy=0,
load_up_to_adc=input_field_location)
# Set up fitting object
psfFit_psd = PSFFitter(xp.real,yp.real,np.abs(Exyz_psd)**2)
# Fit airy pattern and coupling integral
amplitude_guess = np.max(np.abs(Exyz_psd))**2
airyFitted, popt_airy, pcov_airy = psfFit_psd.fit_airy_PSF(initialGuess=(amplitude_guess, 0.0, 0.0, 0.0, 0.03))
popt_airy_list.append(popt_airy)
pcov_airy_list.append([ np.sqrt(pcov_airy[i][i]) for i in range(len(pcov_airy)) ])
# Other properties
original_PSD_curve = wfe.PSD2D[int((mcd.n-1)/2),int((mcd.n-1)/2):]
wfe.calculate_1D_PSD(filteredDataFlag=True)
PSDlist.append(wfe.filteredPSD2D[int((mcd.n-1)/2),int((mcd.n-1)/2):])
surfacelist.append(wfe.filteredData)
# Plot of the surface profile
fig2 = plt.figure(figsize=(12,3))
axi1 = fig2.add_subplot(1, 3, 1)
wavefrontmap = wfe.filteredData.copy()*1e6
wavefrontmap[mask] = np.nan
imi1 = axi1.imshow(wavefrontmap, cmap='jet', origin='lower', extent=[xadc.min(), xadc.max(), yadc.min(), yadc.max()])
axi1.set_xlabel('x (mm)')
axi1.set_ylabel('y (mm)')
axi1.set_xlim([-65,65])
axi1.set_ylim([-65,65])
fig2.colorbar(imi1, ax=axi1, label='Surface height (nm)')
# Plot of the PSD
axi2 = fig2.add_subplot(1, 3, 2)
axi2.loglog(original_PSD_curve, 'k-')
axi2.loglog(PSDlist[-1], 'y-')
axi2.grid(which='major', alpha=0.3)
axi2.grid(which='minor', alpha=0.1)
axi2.set_xlabel('Normalized Radial Frequency ($\\rho/\\rho_c$)')
axi2.set_ylabel('PSD (mm$^4$)')
# Plot of the PSF
axi3 = fig2.add_subplot(1, 3, 3)
differential_amplitude = np.abs(Exyz_og) - np.abs(Exyz_psd)
if (differential_amplitude == 0).all():
norm_par = None
else:
norm_par = LogNorm()
imi3 = axi3.imshow(differential_amplitude, extent=oC, cmap='gray', origin='lower', norm=norm_par)
fig2.colorbar(imi3, ax=axi3, label='Differential amplitude')
axi3.set_xlim([-1,1])
axi3.set_ylim([-1,1])
axi3.set_xlabel('x (mm)')
axi3.set_ylabel('y (mm)')
plt.tight_layout()
plt.savefig('./figures/wfe_frequency_analysis/lowpass_spatial_freq_{:.0f}.png'.format(flist.tolist().index(fmax) + 1), bbox_inches='tight', transparent=True)
plt.close()
# Optimum fit parameters for all scaled surface errors
amplitude_airy, x0_airy, y0_airy, R_airy, offset_airy = np.asarray(popt_airy_list).T
amplitude_airy_err, x0_airy_err, y0_airy_err, R_airy_err, offset_airy_err = np.asarray(pcov_airy_list).T
# Distance from original centroid at (0,0)
r_airy = np.sqrt(x0_airy**2+y0_airy**2)
r_err_airy = np.sqrt( (x0_airy**2*x0_airy_err**2+y0_airy**2*y0_airy_err**2) / (x0_airy**2+y0_airy**2))
r_airy_mas = r_airy * mcd.platescale * 1000
r_err_airy_mas = r_err_airy * mcd.platescale * 1000
save_variable('./data/lowpass_frequency_filter_airy_distance.pkl', (r_airy, r_err_airy))
save_variable('./data/lowpass_frequency_filter_airy_locations.pkl', (x0_airy, y0_airy, x0_airy_err, y0_airy_err))
print('Platescale in mas/mm: ', mcd.platescale * 1000)
################################
### Bandpass filter analysis ###
################################
# Setting up empty lists for fitting process
popt_airy_list, popt_cust_list = [], []
pcov_airy_list, custom_var_list = [], []
Exyzlist, PSDlist, surfacelist = [], [], []
central_frequencies = []
# Do the analysis for each frequency in flist
for fmax in flist:
print('Bandpass - Freq.: {} - Sample {:02d}/{:02d}'.format(fmax, flist.tolist().index(fmax) + 1, len(flist) ) )
if fmax == flist[0]:
# Use a lowpass filter only for the very first element in flist
wfe.set_lowpass_filter(fmax)
central_frequencies.append(fmax)
else:
# Set a bandpass between the current and previous frequency element
i = flist.tolist().index(fmax) - 1
wfe.set_bandpass_filter(flist[i], fmax)
central_frequencies.append(np.mean([flist[i],fmax]))
# Adding the wavefront error
scal = wfe.filteredData
mcd.ELT_ADC.p1.add_wavefront_error(scal)
# Performing the propagation using Fresnel approximation to the focal plane.
Exyz_psd,xp,yp = mcd.propagate(l=l,surface=-1, zoom=False, show=False,
print_progress=False, hx=0, hy=0,
load_up_to_adc=input_field_location)
# Set up fitting object
psfFit_psd = PSFFitter(xp.real,yp.real,np.abs(Exyz_psd)**2)
# Fit airy pattern and coupling integral
amplitude_guess = np.max(np.abs(Exyz_psd))**2
airyFitted, popt_airy, pcov_airy = psfFit_psd.fit_airy_PSF(initialGuess=(amplitude_guess, 0.0, 0.0, 0.0, 0.03))
popt_airy_list.append(popt_airy)
pcov_airy_list.append([ np.sqrt(pcov_airy[i][i]) for i in range(len(pcov_airy)) ])
# Other properties
original_PSD_curve = wfe.PSD2D[int((mcd.n-1)/2),int((mcd.n-1)/2):]
wfe.calculate_1D_PSD(filteredDataFlag=True)
PSDlist.append(wfe.filteredPSD2D[int((mcd.n-1)/2),int((mcd.n-1)/2):])
surfacelist.append(wfe.filteredData)
# Plot of the surface profile
fig2 = plt.figure(figsize=(12,3))
axi1 = fig2.add_subplot(1, 3, 1)
wavefrontmap = wfe.filteredData.copy()*1e6
wavefrontmap[mask] = np.nan
imi1 = axi1.imshow(wavefrontmap, cmap='jet', origin='lower', extent=[xadc.min(), xadc.max(), yadc.min(), yadc.max()])
axi1.set_xlabel('x (mm)')
axi1.set_ylabel('y (mm)')
axi1.set_xlim([-65,65])
axi1.set_ylim([-65,65])
fig2.colorbar(imi1, ax=axi1, label='Surface height (nm)')
# Plot of the PSD
axi2 = fig2.add_subplot(1, 3, 2)
axi2.loglog(original_PSD_curve, 'k-')
axi2.loglog(PSDlist[-1], 'y-')
axi2.grid(which='major', alpha=0.3)
axi2.grid(which='minor', alpha=0.1)
axi2.set_xlabel('Normalized Radial Frequency ($\\rho/\\rho_c$)')
axi2.set_ylabel('PSD (mm$^4$)')
# Plot of the PSF
axi3 = fig2.add_subplot(1, 3, 3)
differential_amplitude = np.abs(Exyz_og) - np.abs(Exyz_psd)
if (differential_amplitude == 0).all():
norm_par = None
else:
norm_par = LogNorm()
imi3 = axi3.imshow(differential_amplitude, extent=oC, cmap='gray', origin='lower', norm=norm_par)
fig2.colorbar(imi3, ax=axi3, label='Differential amplitude')
axi3.set_xlim([-1,1])
axi3.set_ylim([-1,1])
axi3.set_xlabel('x (mm)')
axi3.set_ylabel('y (mm)')
plt.tight_layout()
plt.savefig('./figures/wfe_frequency_analysis/bandpass_spatial_freq_{:.0f}.png'.format(flist.tolist().index(fmax) + 1), bbox_inches='tight', transparent=True)
plt.close()
# Optimum fit parameters for all scaled surface errors
amplitude_airy, x0_airy, y0_airy, R_airy, offset_airy = np.asarray(popt_airy_list).T
amplitude_airy_err, x0_airy_err, y0_airy_err, R_airy_err, offset_airy_err = np.asarray(pcov_airy_list).T
# Distance from original centroid at (0,0)
r_airy = np.sqrt(x0_airy**2+y0_airy**2)
r_err_airy = np.sqrt( (x0_airy**2*x0_airy_err**2+y0_airy**2*y0_airy_err**2) / (x0_airy**2+y0_airy**2))
r_airy_mas = r_airy * mcd.platescale * 1000
r_err_airy_mas = r_err_airy * mcd.platescale * 1000
save_variable('./data/bandpass_frequency_filter_airy_distance.pkl', (r_airy, r_err_airy))
save_variable('./data/bandpass_frequency_filter_airy_locations.pkl', (x0_airy, y0_airy, x0_airy_err, y0_airy_err))
save_variable('./data/bandpass_frequency_filter_central_frequencies.pkl', central_frequencies)
print('Platescale in mas/mm: ', mcd.platescale * 1000)