mirror of
https://github.com/yosata/fourierOptics.git
synced 2026-10-29 20:51:10 +00:00
809 lines
30 KiB
Python
809 lines
30 KiB
Python
import numpy as np
|
|
from .fourier import *
|
|
from .gaussian import *
|
|
from .opticalTrain import *
|
|
from .optics import *
|
|
from .profiles import *
|
|
from .psfFitter import *
|
|
from .wavefrontErrors import *
|
|
from .misc import *
|
|
|
|
|
|
'''
|
|
Changelog:
|
|
|
|
Changes w.r.t. the old MICADO paraxial model:
|
|
- Changed focal length of collimator lens from 4278.32701 to 1601.688 mm
|
|
- Changed distance from ADC to collimator lens from 50 mm to 74 mm.
|
|
- Changed thickness of ADC prisms from 25 to 15 mm
|
|
- Changed working F-number (WFNO) from 53.4466 to 20.0269
|
|
- This results in a change of the plate scale from 0.1001 arcsec/mm to 0.2672 arcsec/mm
|
|
'''
|
|
|
|
class ParaxialMICADO:
|
|
def __init__(self, l=1.35e-3, n=1001, m=1001, dpix=160, entrance_pupil='ELT'):
|
|
self.l = l
|
|
self.dpix = dpix
|
|
self.n, self.m = n, m
|
|
self.x, self.y = np.meshgrid(np.linspace(-n/2*dpix, n/2*dpix, n), np.linspace(-m/2*dpix, m/2*dpix, m))
|
|
if entrance_pupil == 'ELT':
|
|
self.ELT_MASK = Profiles().ELT_pupil(n,m,dpix).astype('complex128')
|
|
elif entrance_pupil == 'obscured':
|
|
r1 = 38542/2
|
|
r2 = 10700/2
|
|
self.ELT_MASK = Profiles().obscured_top_hat(r1, r2, n, m, dpix).astype('complex128')
|
|
elif entrance_pupil == 'open':
|
|
r1 = 38542/2
|
|
self.ELT_MASK = Profiles().top_hat(r1, n, m, dpix).astype('complex128')
|
|
|
|
# Built optical system elements
|
|
self.ELT_MIRROR = ParabolicMirror(dx=0, dy=0, name='ELT-paraxial', CA=38542, f=6.84022e5)
|
|
self.ELT_FP = OpticalSurface(name='MCD-IFP')
|
|
self.ELT_COL = ParaxialLens(CA=154.54, f=1418.94, name='MCD-COL')
|
|
self.ELT_ADC = ADCObject()
|
|
self.ELT_CAM = ParaxialLens(CA=89.2, f=1601.688, name='MCD-CAM')
|
|
self.ELT_DET = OpticalSurface(name='MCD-DET')
|
|
|
|
# Built optical train
|
|
self.MCD = OpticalTrain()
|
|
self.MCD.add_object(self.ELT_MIRROR, dist=0)
|
|
self.MCD.add_object(self.ELT_FP, dist=self.ELT_MIRROR.f)
|
|
self.MCD.add_object(self.ELT_COL, dist=self.ELT_COL.f)
|
|
self.ELT_ADC.add_to_optical_train(self.MCD, 1209.4718)
|
|
self.MCD.add_object(self.ELT_CAM, dist=74)
|
|
self.MCD.add_object(self.ELT_DET, dist=self.ELT_CAM.f)
|
|
|
|
self.last_PSF = None
|
|
self.clean_PSF = None
|
|
self.pixelscale = 4e-3 # arcseconds/pixel
|
|
self.platescale = self.set_plate_scale()
|
|
print ('Your resolution is {}x{} with a pixel size of {}'.format(self.n,self.m,self.dpix))
|
|
print('This means your grid is {} mm by {} mm, with each pixel {} mm.'.format(
|
|
self.x.max()-self.x.min(), self.y.max()-self.y.min(), self.dpix))
|
|
|
|
|
|
def apply_mask(self, mask, replace=True):
|
|
'''
|
|
Set a mask to the ELT entrance pupil.
|
|
'''
|
|
if replace:
|
|
self.ELT_MASK = mask.astype('complex128')
|
|
elif not replace:
|
|
self.ELT_MASK *= mask
|
|
|
|
|
|
def set_PSF(self, PSF, x, y, perfect=True):
|
|
'''
|
|
If calculating another PSF takes to long,
|
|
one can set a custom PSF using this
|
|
function.
|
|
|
|
Can be set as the 'clean' PSF or as another
|
|
'last' PSF, with the perfect flag.
|
|
'''
|
|
if perfect:
|
|
self.clean_PSF = (PSF, x, y)
|
|
elif not perfect:
|
|
self.last_PSF = (PSF, x, y)
|
|
|
|
|
|
def centroid_shift(self, method='Coupling', reference_PSF=None, **kwargs):
|
|
'''
|
|
Do a centroid shift calculation for the given method.
|
|
This is a quick way of doing it. It only returns the
|
|
centroid shift (and uncertainties) and no other parameters.
|
|
'''
|
|
assert (self.last_PSF is not None), 'PSF necessary for fitting.'
|
|
if 'initialGuess' in kwargs:
|
|
guess_value = kwargs.get('initialGuess')
|
|
|
|
if reference_PSF is None:
|
|
PSF, x, y = self.last_PSF
|
|
elif reference_PSF is not None:
|
|
PSF, x, y = reference_PSF
|
|
fitter = PSFFitter(x.real,y.real,np.abs(PSF)**2)
|
|
|
|
if method == 'Coupling':
|
|
assert (self.clean_PSF is not None), 'Perfect PSF necessary for comparison.'
|
|
Eclean, xclean, yclean = self.clean_PSF
|
|
fitted_PSF, popt, var, coupling = fitter.fit_custom_field(Eclean, PSF, return_coupling=True, **kwargs)
|
|
x0, y0 = np.asarray(popt).T
|
|
sx0, sy0 = np.asarray(np.sqrt(var)).T
|
|
return x0, y0, sx0, sy0, coupling
|
|
|
|
elif method == 'Airy':
|
|
if 'initialGuess' not in kwargs:
|
|
guess_value = (0.03**2, 0.0, 0.0, 0.0, 0.03)
|
|
fitted_PSF, popt, pcov = fitter.fit_airy_PSF(initialGuess=guess_value)
|
|
_, x0, y0, _, _ = popt
|
|
pcov_list = [ np.sqrt(pcov[i][i]) for i in range(len(pcov)) ]
|
|
_, sx0, sy0, _, _ = np.asarray(pcov_list).T
|
|
|
|
elif method == 'Obscured Airy':
|
|
if 'initialGuess' not in kwargs:
|
|
guess_value = (0.03**2, 0.0, 0.0, 0.0, 0.03)
|
|
fitted_PSF, popt, pcov = fitter.fit_fixed_obscuration_airy_PSF(initialGuess=guess_value)
|
|
_, x0, y0, _, _ = popt
|
|
pcov_list = [ np.sqrt(pcov[i][i]) for i in range(len(pcov)) ]
|
|
_, sx0, sy0, _, _ = np.asarray(pcov_list).T
|
|
|
|
elif method == 'Gaussian':
|
|
if 'initialGuess' not in kwargs:
|
|
guess_value = (0.04, 0, 0, 0.01, 0.01, 0, 0)
|
|
fitted_PSF, popt, pcov = fitter.fit_gaussian_PSF(initialGuess=guess_value)
|
|
_, x0, y0, _, _, _, _ = popt
|
|
pcov_list = [ np.sqrt(pcov[i][i]) for i in range(len(pcov)) ]
|
|
_, sx0, sy0, _, _, _, _ = np.asarray(pcov_list).T
|
|
|
|
elif method == 'Moffat':
|
|
if 'initialGuess' not in kwargs:
|
|
guess_value = (0.04, 0, 0, 0.01, 0.01, 0, 0, 4)
|
|
fitted_PSF, popt, pcov = fitter.fit_moffat_PSF(initialGuess=guess_value)
|
|
_, x0, y0, _, _, _, _, _ = popt
|
|
pcov_list = [ np.sqrt(pcov[i][i]) for i in range(len(pcov)) ]
|
|
_, sx0, sy0, _, _, _, _, _ = np.asarray(pcov_list).T
|
|
|
|
elif method == 'Barycenter':
|
|
x0, y0, sx0, sy0 = fitter.fit_barycenter(amplitude=False)
|
|
|
|
return x0, y0, sx0, sy0
|
|
|
|
|
|
def propagate(self, l=None, surface=-1, zoom=False, hx=0, hy=0, load_up_to_adc=None,
|
|
coordinates=None, show=False, image_output=None, save=None, apply_interpolation=True,
|
|
print_progress=True, defocus=None):
|
|
'''
|
|
Propagate the input wavefront through the MICADO paraxial
|
|
model. Optionally the propagation starts at the ADC, if
|
|
a data file with the wavefront at that point is defined.
|
|
|
|
Parameters:
|
|
l - Desired wavelength of the propagation
|
|
surface - Surface to propagate to. Default: -1 = Detector plane
|
|
zoom - Zoom in on the PSF on the detector?
|
|
load_up_to_adc - Use a .pkl file to load in the wavefront just before the adc.
|
|
coordinates - User defined coordinates that define the propagation sampling.
|
|
show_final - Plots the final amplitude and phase if True.
|
|
|
|
Returns:
|
|
Exyz - Complex field with shape n,m describing the amplitude and phase
|
|
of the wavefront at the output surface.
|
|
x, y - Corresponding x and y coordinates, in mm.
|
|
'''
|
|
# Set up wavelength
|
|
l = self.l if not l else l
|
|
|
|
# Setting up the arguments for each surface propgation
|
|
surfaces = self.get_surfaces()
|
|
surface = len(surfaces)-1 if surface == -1 else surface
|
|
coordinatesList = self.get_coordinates(coordinates)
|
|
show,show_final_zoom= self.get_show_image(show, zoom, len(surfaces), surface)
|
|
output_coordinates = self.get_image_coordinates(image_output, coordinatesList, len(surfaces), surface)
|
|
saveList = self.get_save_image(save, len(surfaces), surface)
|
|
apply_ip = self.get_image_interpolation(apply_interpolation, zoom, len(surfaces), surface)
|
|
tilt_x, tilt_y = self.get_tilt_angles(hx, hy)
|
|
|
|
# Set up the FourierOptics object for propagation
|
|
fou = FourierOptics(l=l)
|
|
x, y = self.x, self.y
|
|
p = 1 # Used as counter for the ADC prisms.
|
|
|
|
# Load the data up to the ADC (useful for many iterations on the same
|
|
# calculation, where differences only appear at or after the ADC)
|
|
if load_up_to_adc is not None:
|
|
Eout, x, y = load_variable(load_up_to_adc)
|
|
x = x.real
|
|
y = y.real
|
|
s = 4
|
|
elif load_up_to_adc is None:
|
|
s = 0
|
|
|
|
# Start propgation
|
|
if print_progress: print('Starting propagation...')
|
|
while s <= surface:
|
|
if (surfaces[s] == self.ELT_MASK).all():
|
|
Ein = surfaces[s]
|
|
Eout = surfaces[s]
|
|
if show[s] or saveList[s]:
|
|
_fig = plt.figure(figsize=(8,3))
|
|
_ax = _fig.add_subplot(121)
|
|
_im = _ax.imshow(np.abs(Eout), cmap='hot', extent=[x.min(), x.max(), y.min(), y.max()])
|
|
_ax.set_xlim([x.min(), x.max()])
|
|
_ax.set_ylim([y.min(), y.max()])
|
|
_ax.set_xlabel('x [mm]')
|
|
_ax.set_ylabel('y [mm]')
|
|
_fig.colorbar(_im, ax=_ax, label='Amplitude')
|
|
_ax2 = _fig.add_subplot(122)
|
|
_im2 = _ax2.imshow(np.angle(Eout), cmap='hot', extent=[x.min(), x.max(), y.min(), y.max()])
|
|
_ax2.set_xlim([x.min(), x.max()])
|
|
_ax2.set_ylim([y.min(), y.max()])
|
|
_ax2.set_xlabel('x [mm]')
|
|
_ax2.set_ylabel('y [mm]')
|
|
_fig.colorbar(_im2, ax=_ax2, label='Phase')
|
|
plt.tight_layout()
|
|
if saveList[s]:
|
|
plt.savefig(saveList[s], bbox_inches='tight', transparent=True)
|
|
if show[s]:
|
|
plt.show()
|
|
else:
|
|
plt.close()
|
|
|
|
|
|
elif surfaces[s] == self.ELT_MIRROR:
|
|
if print_progress: print('Propagating from ELT-M1 to ELT-IFP')
|
|
Ein = Eout * self.ELT_MIRROR.phase_delay(x,y,l,paraxial=True)
|
|
if s == surface and zoom:
|
|
xzoom, yzoom = x, y
|
|
Einzoom = Ein
|
|
fzoom = self.ELT_MIRROR.f
|
|
Eout, x, y = fou.fresnel_propagation(Ein, x, y, dz=self.ELT_MIRROR.f, oversample=None,
|
|
show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s],
|
|
apply_interpolation=apply_ip[s], tx=tilt_x[s], ty=tilt_y[s])
|
|
|
|
elif surfaces[s] == self.ELT_FP:
|
|
if print_progress: print('Propagating from ELT-IFP to MCD-COL')
|
|
Ein = Eout
|
|
if self.ELT_FP.wavefrontError:
|
|
Ein *= self.ELT_FP.phase_delay(x, y, l, ip_type='phase')
|
|
Eout, x, y = fou.fresnel_propagation(Ein, x, y, dz=self.ELT_COL.f, oversample=None,
|
|
show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s],
|
|
apply_interpolation=apply_ip[s], tx=tilt_x[s], ty=tilt_y[s])
|
|
|
|
elif surfaces[s] == self.ELT_COL:
|
|
if print_progress: print('Propagating from MCD-COL to MCD-ADC-P1')
|
|
x = x.real
|
|
y = y.real
|
|
Ein = Eout * self.ELT_COL.phase_delay(x,y,l, paraxial=True)
|
|
Eout, x, y = fou.plane_wave_propagation(Ein, x, y, dz=self.MCD.sequentialDistance[3], oversample=None,
|
|
show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], tx=tilt_x[s], ty=tilt_y[s])
|
|
|
|
elif surfaces[s] == self.ELT_ADC and p == 1:
|
|
if print_progress: print('Propagating from MCD-ADC-P1 to MCD-ADC-P2')
|
|
Ein = Eout * self.ELT_ADC.get_phase_delay(x,y,l, theta=None, paraxial=True, prism=1)
|
|
Eout, x, y = fou.plane_wave_propagation(Ein, x, y, dz=self.MCD.sequentialDistance[4], oversample=None,
|
|
show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], tx=tilt_x[s], ty=tilt_y[s])
|
|
p += 1
|
|
|
|
elif surfaces[s] == self.ELT_ADC and p == 2:
|
|
if print_progress: print('Propagating from MCD-ADC-P2 to MCD-ADC-P3')
|
|
Ein = Eout * self.ELT_ADC.get_phase_delay(x,y,l, theta=None, paraxial=True, prism=2)
|
|
Eout, x, y = fou.plane_wave_propagation(Ein, x, y, dz=self.MCD.sequentialDistance[5], oversample=None,
|
|
show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], tx=tilt_x[s], ty=tilt_y[s])
|
|
p += 1
|
|
|
|
elif surfaces[s] == self.ELT_ADC and p == 3:
|
|
if print_progress: print('Propagating from MCD-ADC-P3 to MCD-ADC-P4')
|
|
Ein = Eout * self.ELT_ADC.get_phase_delay(x,y,l, theta=None, paraxial=True, prism=3)
|
|
Eout, x, y = fou.plane_wave_propagation(Ein, x, y, dz=self.MCD.sequentialDistance[6], oversample=None,
|
|
show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], tx=tilt_x[s], ty=tilt_y[s])
|
|
p += 1
|
|
|
|
elif surfaces[s] == self.ELT_ADC and p == 4:
|
|
if print_progress: print('Propagating from MCD-ADC-P4 to MCD-CAM')
|
|
Ein = Eout * self.ELT_ADC.get_phase_delay(x,y,l, theta=None, paraxial=True, prism=4)
|
|
Eout, x, y = fou.plane_wave_propagation(Ein, x, y, dz=self.MCD.sequentialDistance[7], oversample=None,
|
|
show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s], tx=tilt_x[s], ty=tilt_y[s])
|
|
p += 1
|
|
|
|
elif surfaces[s] == self.ELT_DET:
|
|
if print_progress: print('Propagating from MCD-CAM to MCD-DET')
|
|
Ein = Eout * self.ELT_CAM.phase_delay(x,y,l, paraxial=True)
|
|
if defocus is None:
|
|
defocus = 0
|
|
if s == surface and zoom:
|
|
xzoom, yzoom = x.real, y.real
|
|
Einzoom = Ein
|
|
fzoom = self.ELT_CAM.f + defocus
|
|
Eout, x, y = fou.fresnel_propagation(Ein, x, y, dz=self.ELT_CAM.f+defocus, oversample=None,
|
|
show=show[s], save=saveList[s], outputCoordinates=output_coordinates[s],
|
|
apply_interpolation=apply_ip[s], tx=tilt_x[s], ty=tilt_y[s])
|
|
s += 1
|
|
|
|
if zoom:
|
|
if print_progress: print('Zooming in on the PSF location...')
|
|
imax_1, imax_2 = find_maximum(np.abs(Eout))
|
|
xmax = x[imax_1, imax_2]
|
|
ymax = y[imax_1, imax_2]
|
|
ind = s-3 if (s-3)>0 else 0
|
|
newCoordinates = np.asarray(coordinatesList[ind]) + np.array([xmax, xmax, ymax, ymax])
|
|
newCoordinates = newCoordinates.real.tolist()
|
|
Eout, x, y = fou.fresnel_propagation(Einzoom, xzoom, yzoom, dz=fzoom, oversample=None,
|
|
show=show_final_zoom, save=saveList[s-1], outputCoordinates=newCoordinates,
|
|
apply_interpolation=True, interpol_type='linear', tx=tilt_x[s-1], ty=tilt_y[s-1])
|
|
|
|
if print_progress: print('Propagation finished!')
|
|
self.last_PSF = (Eout, x, y)
|
|
return Eout, x, y
|
|
|
|
|
|
|
|
def get_surfaces(self):
|
|
_surfaces = [
|
|
self.ELT_MASK,
|
|
self.ELT_MIRROR,
|
|
self.ELT_FP,
|
|
self.ELT_COL,
|
|
self.ELT_ADC,
|
|
self.ELT_ADC,
|
|
self.ELT_ADC,
|
|
self.ELT_ADC,
|
|
self.ELT_CAM,
|
|
self.ELT_DET
|
|
]
|
|
return _surfaces
|
|
|
|
|
|
def get_coordinates(self, coordinates):
|
|
# Output coordinates:
|
|
if coordinates is None:
|
|
_coordinatesList = [
|
|
[-1.0, 1.0, -1.0, 1.0],
|
|
[-200, 200, -200, 200],
|
|
[-120, 120, -120, 120],
|
|
[-120, 120, -120, 120],
|
|
[-120, 120, -120, 120],
|
|
[-120, 120, -120, 120],
|
|
[-120, 120, -120, 120],
|
|
[-2.5/2.669, 2.5/2.669, -2.5/2.669, 2.5/2.669]
|
|
]
|
|
elif coordinates is not None:
|
|
_coordinatesList = coordinates
|
|
return _coordinatesList
|
|
|
|
def get_show_image(self, show, zoom, N, surf):
|
|
# Set show values. Will only show the image plane.
|
|
_showlist = [False] * N
|
|
_showlist[surf] = show
|
|
_show_final_zoom = False
|
|
if zoom:
|
|
_show_final_zoom = show
|
|
_showlist[surf] = False
|
|
_show = _showlist
|
|
return _show, _show_final_zoom
|
|
|
|
def get_image_coordinates(self, image_output, coordinates, N, surf):
|
|
# Set the image coordinates
|
|
_output_coordinates = [None] * N
|
|
_output_coordinates[2] = coordinates[1]
|
|
if image_output is not None:
|
|
_output_coordinates[surf] = image_output
|
|
return _output_coordinates
|
|
|
|
def get_save_image(self, save, N, surf):
|
|
# Set the save values
|
|
_savelist = [False] * N
|
|
if save == 'all':
|
|
_savelist = [
|
|
'01_ELT_MASK_output.png',
|
|
'02_ELT_FP_output.png',
|
|
'03_ELT_MCD_COL_output.png',
|
|
'04_ELT_MCD_ADC_P1_output.png',
|
|
'05_ELT_MCD_ADC_P2_output.png',
|
|
'06_ELT_MCD_ADC_P3_output.png',
|
|
'07_ELT_MCD_ADC_P4_output.png',
|
|
'08_ELT_MCD_CAM_output.png',
|
|
'09_ELT_MCD_DET_output.png',
|
|
'10_ELT_MCD_DET_output_zoomed.png'
|
|
]
|
|
elif save is not None:
|
|
_savelist[surf] = save
|
|
return _savelist
|
|
|
|
def get_image_interpolation(self, apply_interpolation, zoom, N, surf):
|
|
# Apply interpolation to the output?
|
|
_apply_ip = [False] * N
|
|
if not zoom and apply_interpolation and surf!=2:
|
|
_apply_ip[surf] = True
|
|
return _apply_ip
|
|
|
|
def set_plate_scale(self, wfno=20.0269):
|
|
'''
|
|
Returns the platescale of this paraxial MICADO
|
|
model in arcseconds/mm.
|
|
'''
|
|
_ps = (3600*180/np.pi) / (self.ELT_MIRROR.CA * wfno)
|
|
self.platescale = _ps
|
|
return _ps
|
|
|
|
def get_field_angle(self,hx,hy):
|
|
fx = hx * 0.0075
|
|
fy = hy * 0.0075
|
|
return fx, fy
|
|
|
|
def get_tilt_angles(self, hx, hy):
|
|
'''
|
|
Returns the propagation angles at each surface
|
|
in degrees. E.g. near the ADC for hx=1, tx=3.61
|
|
degrees.
|
|
'''
|
|
_x,_y,_tx,_ty,_z = self.perform_ray_trace_without_adc(hx,hy,0,0)
|
|
_tx[0] = _tx[1]
|
|
_ty[0] = _ty[1]
|
|
tx = np.asarray([_tx[i] for i in [0,1,2,3,5,7,9,11,12,13,13]]).flatten()
|
|
ty = np.asarray([_ty[i] for i in [0,1,2,3,5,7,9,11,12,13,13]]).flatten()
|
|
return tx, ty
|
|
|
|
|
|
def perform_ray_trace(self,hx,hy,px,py):
|
|
'''
|
|
Ray trace the paraxial model of ELT-MICADO
|
|
|
|
Parameters
|
|
----------
|
|
hx,hy - float or array
|
|
Normalized field angles
|
|
px,py - float or array
|
|
Normalized pupil angles
|
|
|
|
Returns
|
|
-------
|
|
xout - float or array
|
|
x-coordinates on all surfaces
|
|
yout - float or array
|
|
y-coordinates on all surfaces
|
|
zout
|
|
z-coordinates of all surfaces
|
|
|
|
'''
|
|
xout = []
|
|
yout = []
|
|
|
|
# Show simplified 2D ray trace model
|
|
if type(px)==int or type(px)==float:
|
|
px = np.array([px])
|
|
if type(py)==int or type(py)==float:
|
|
py = np.array([py])
|
|
if type(hx)==int or type(hx)==float:
|
|
hx = np.array([hx])
|
|
if type(hy)==int or type(hy)==float:
|
|
hy = np.array([hy])
|
|
|
|
xp0 = np.asarray(px) * self.ELT_MIRROR.CA/2
|
|
yp0 = np.asarray(py) * self.ELT_MIRROR.CA/2
|
|
|
|
# for xp in range(len(xp0)):
|
|
positions_and_angles = []
|
|
|
|
# Calculate the field angles
|
|
txin, tyin = self.get_field_angle(hx, hy)
|
|
txin = np.deg2rad(txin)
|
|
tyin = np.deg2rad(tyin)
|
|
|
|
#Start of system (perhaps at ELT_MASK?)
|
|
xp, yp, tx, ty = xp0, yp0, txin, tyin
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # Mask
|
|
|
|
# ELT M1
|
|
xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_MIRROR.f, self.ELT_MIRROR.f)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT M1
|
|
xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, self.ELT_MIRROR.f)
|
|
|
|
# ELT-FP
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT FP
|
|
xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, self.ELT_COL.f)
|
|
|
|
# ELT-MCD-COL
|
|
xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_COL.f, self.ELT_COL.f)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT MCD COL
|
|
xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, 1209.4718)
|
|
|
|
# ELT-MCD-ADC
|
|
# First prism:
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
xp, yp, tx, ty = get_prism_rtm( xp,yp,tx,ty,
|
|
thickness=self.ELT_ADC.p1.thickness,
|
|
a1x=np.deg2rad(self.ELT_ADC.p1.tilt1x),
|
|
a2x=np.deg2rad(self.ELT_ADC.p1.tilt2x),
|
|
a1y=np.deg2rad(self.ELT_ADC.p1.tilt1y),
|
|
a2y=np.deg2rad(self.ELT_ADC.p1.tilt2y),
|
|
n_glass=self.ELT_ADC.p1.find_refractive_index(self.l, unit='millimeter'))
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,1)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
|
|
# Second prism:
|
|
xp, yp, tx,ty = get_prism_rtm( xp,yp,tx,ty,
|
|
thickness=self.ELT_ADC.p2.thickness,
|
|
a1x=np.deg2rad(self.ELT_ADC.p2.tilt1x),
|
|
a2x=np.deg2rad(self.ELT_ADC.p2.tilt2x),
|
|
a1y=np.deg2rad(self.ELT_ADC.p2.tilt1y),
|
|
a2y=np.deg2rad(self.ELT_ADC.p2.tilt2y),
|
|
n_glass=self.ELT_ADC.p2.find_refractive_index(self.l, unit='millimeter'))
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,8)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
|
|
# Third prism
|
|
xp, yp, tx,ty = get_prism_rtm( xp,yp,tx,ty,
|
|
thickness=self.ELT_ADC.p3.thickness,
|
|
a1x=np.deg2rad(self.ELT_ADC.p3.tilt1x),
|
|
a2x=np.deg2rad(self.ELT_ADC.p3.tilt2x),
|
|
a1y=np.deg2rad(self.ELT_ADC.p3.tilt1y),
|
|
a2y=np.deg2rad(self.ELT_ADC.p3.tilt2y),
|
|
n_glass=self.ELT_ADC.p3.find_refractive_index(self.l, unit='millimeter'))
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,1)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
|
|
# Fourth prism
|
|
xp, yp, tx,ty = get_prism_rtm( xp,yp,tx,ty,
|
|
thickness=self.ELT_ADC.p4.thickness,
|
|
a1x=np.deg2rad(self.ELT_ADC.p4.tilt1x),
|
|
a2x=np.deg2rad(self.ELT_ADC.p4.tilt2x),
|
|
a1y=np.deg2rad(self.ELT_ADC.p4.tilt1y),
|
|
a2y=np.deg2rad(self.ELT_ADC.p4.tilt2y),
|
|
n_glass=self.ELT_ADC.p4.find_refractive_index(self.l, unit='millimeter'))
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,74)
|
|
|
|
# ELT-MCD-CAM:
|
|
xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_CAM.f, self.ELT_CAM.f)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # After ELT-CAM
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_CAM.f)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # Image
|
|
|
|
x_out = np.asarray(positions_and_angles)[:,0]
|
|
y_out = np.asarray(positions_and_angles)[:,1]
|
|
tx_out = np.asarray(positions_and_angles)[:,2]
|
|
ty_out = np.asarray(positions_and_angles)[:,3]
|
|
z_out = np.asarray([0, # ELT Mask
|
|
0, # ELT M1
|
|
6.84022e5, # ELT-FP
|
|
6.84022e5+1418.94, # ELT-MCD-COL
|
|
6.84022e5+1418.94+1209.4718, # ADC
|
|
6.84022e5+1418.94+1209.4718 + 15, # ADC p1
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1, # Between p1 and p2
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15, # ADC p2
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8, # Between the prisms
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15, # ADC p3
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1, # Between p3 and p4
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15, # ADC p4
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 74, # MCD-CAM
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 74 + 1601.688 # Focal plane
|
|
])
|
|
return x_out, y_out, tx_out, ty_out, z_out
|
|
|
|
|
|
def perform_ray_trace_without_adc(self,hx,hy,px,py):
|
|
'''
|
|
Ray trace the paraxial model of ELT-MICADO
|
|
without an ADC in the optical path.
|
|
|
|
Parameters
|
|
----------
|
|
hx,hy - float or array
|
|
Normalized field angles
|
|
px,py - float or array
|
|
Normalized pupil angles
|
|
|
|
Returns
|
|
-------
|
|
xout - float or array
|
|
x-coordinates on all surfaces
|
|
yout - float or array
|
|
y-coordinates on all surfaces
|
|
zout
|
|
z-coordinates of all surfaces
|
|
|
|
'''
|
|
xout = []
|
|
yout = []
|
|
|
|
# Show simplified 2D ray trace model
|
|
if type(px)==int or type(px)==float:
|
|
px = np.array([px])
|
|
if type(py)==int or type(py)==float:
|
|
py = np.array([py])
|
|
if type(hx)==int or type(hx)==float:
|
|
hx = np.array([hx])
|
|
if type(hy)==int or type(hy)==float:
|
|
hy = np.array([hy])
|
|
|
|
xp0 = np.asarray(px) * self.ELT_MIRROR.CA/2
|
|
yp0 = np.asarray(py) * self.ELT_MIRROR.CA/2
|
|
|
|
# for xp in range(len(xp0)):
|
|
positions_and_angles = []
|
|
# l_ind = self.l * 1e3 # wavelength from mm in microns
|
|
|
|
# Calculate the field angles
|
|
txin, tyin = self.get_field_angle(hx, hy)
|
|
txin = np.deg2rad(txin)
|
|
tyin = np.deg2rad(tyin)
|
|
|
|
#Start of system (perhaps at ELT_MASK?)
|
|
xp, yp, tx, ty = xp0, yp0, txin, tyin
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # Mask
|
|
|
|
# ELT M1
|
|
xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_MIRROR.f, self.ELT_MIRROR.f)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT M1
|
|
xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, self.ELT_MIRROR.f)
|
|
|
|
# ELT-FP
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT FP
|
|
xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, self.ELT_COL.f)
|
|
|
|
# ELT-MCD-COL
|
|
xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_COL.f, self.ELT_COL.f)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # ELT MCD COL
|
|
xp, yp, tx, ty = get_free_space_propagation_rtm(xp, yp, tx, ty, 1209.4718)
|
|
|
|
# ELT-MCD-ADC
|
|
# First prism:
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_ADC.p1.thickness)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,1)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
|
|
# Second prism:
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_ADC.p2.thickness)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,8)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
|
|
# Third prism
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_ADC.p3.thickness)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,1)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
|
|
# Fourth prism
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_ADC.p4.thickness)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)])
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,74)
|
|
|
|
# ELT-MCD-CAM:
|
|
xp, yp, tx, ty = get_paraxial_lens_rtm(xp, yp, tx, ty, self.ELT_CAM.f, self.ELT_CAM.f)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # After ELT-CAM
|
|
xp, yp, tx,ty = get_free_space_propagation_rtm(xp,yp,tx,ty,self.ELT_CAM.f)
|
|
positions_and_angles.append([xp,yp,np.degrees(tx),np.degrees(ty)]) # Image
|
|
|
|
x_out = np.asarray(positions_and_angles)[:,0]
|
|
y_out = np.asarray(positions_and_angles)[:,1]
|
|
tx_out = np.asarray(positions_and_angles)[:,2]
|
|
ty_out = np.asarray(positions_and_angles)[:,3]
|
|
z_out = np.asarray([0, # ELT Mask
|
|
0, # ELT M1
|
|
6.84022e5, # ELT-FP
|
|
6.84022e5+1418.94, # ELT-MCD-COL
|
|
6.84022e5+1418.94+1209.4718, # ADC
|
|
6.84022e5+1418.94+1209.4718 + 15, # ADC p1
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1, # Between p1 and p2
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15, # ADC p2
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8, # Between the prisms
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15, # ADC p3
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1, # Between p3 and p4
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15, # ADC p4
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 74, # MCD-CAM
|
|
6.84022e5+1418.94+1209.4718 + 15 + 1 + 15 + 8 + 15 + 1 + 15 + 74 + 1601.688 # Focal plane
|
|
])
|
|
return x_out, y_out, tx_out, ty_out, z_out
|
|
|
|
|
|
def plot_ray_trace(self, no_adc=False):
|
|
'''
|
|
Function that plots a diagram of the ray traced paraxial
|
|
model of the MICADO instrument. Note that the prisms of
|
|
the ADC do not show their true shape.
|
|
'''
|
|
|
|
px = np.linspace(-1,1,21)
|
|
py = np.zeros(px.shape)
|
|
hx = np.asarray([0]*21)
|
|
hy = np.zeros(hx.shape)
|
|
if no_adc:
|
|
x0, y0, _, _, z0 = self.perform_ray_trace_without_adc(hx,hy,px,py)
|
|
hx = np.asarray([1]*21)
|
|
x1, y1, _, _, z1 = self.perform_ray_trace_without_adc(hx,hy,px,py)
|
|
hx = np.asarray([-1]*21)
|
|
x2, y2, _, _, z2 = self.perform_ray_trace_without_adc(hx,hy,px,py)
|
|
else:
|
|
x0, y0, _, _, z0 = self.perform_ray_trace(hx,hy,px,py)
|
|
hx = np.asarray([1]*21)
|
|
x1, y1, _, _, z1 = self.perform_ray_trace(hx,hy,px,py)
|
|
hx = np.asarray([-1]*21)
|
|
x2, y2, _, _, z2 = self.perform_ray_trace(hx,hy,px,py)
|
|
|
|
fig = plt.figure(figsize=(15,6))
|
|
ax = fig.add_subplot(111)
|
|
for i in range(len(px)):
|
|
ax.plot(z0, x0[:,i], 'ro')
|
|
ax.plot(z0, x0[:,i], 'r-')
|
|
ax.plot(z1, x1[:,i], 'bo')
|
|
ax.plot(z1, x1[:,i], 'b-')
|
|
ax.plot(z2, x2[:,i], 'go')
|
|
ax.plot(z2, x2[:,i], 'g-')
|
|
ax.set_xlim([6.84e5,6.915e5])
|
|
ax.set_ylim([-290,290])
|
|
plt.grid()
|
|
plt.show()
|
|
|
|
|
|
|
|
|
|
class ADC_surfaces:
|
|
'''
|
|
Very specific class to generate and scale wavefront errors on
|
|
the MICADO ADC prisms.
|
|
'''
|
|
def __init__(self, x,y, clearAperture=125):
|
|
self.n, self.m = x.shape
|
|
self.clearAperture = clearAperture
|
|
self.radius = self.clearAperture / 2
|
|
self.yz,self.xz = np.mgrid[x.min()/self.radius:x.max()/self.radius:self.n*1j,
|
|
x.min()/self.radius:x.max()/self.radius:self.m*1j]
|
|
self.zern_coefficients = None
|
|
self.zern_surface = None
|
|
self.psd_surface = None
|
|
self.latest_surface = None
|
|
|
|
|
|
def generate_ADC_wfe(self, zernRMS=20e-6, psdRMS=15e-6, zernModes=19):
|
|
'''
|
|
We apply the wavefront error an each of the ADC prisms
|
|
as a phase transformation, corresponding to a
|
|
transmitted wavefront error.
|
|
|
|
This wfe is separated into a zernike and psd component
|
|
with a total rms of 25 nm
|
|
zernike component - RMS: 20 nm
|
|
psd component - RMS: 15 nm
|
|
Total - RMS: 25 nm
|
|
'''
|
|
print('Generating new ADC WFE...')
|
|
wfe = WavefrontError(self.n,self.m)
|
|
zern_rms = zernRMS # RMS of the zernike component of the WFE
|
|
midspat_rms = psdRMS # RMS of the PSD component of the WFE
|
|
|
|
self.psd_surface = wfe.generate_height_map(midspat_rms, 2, 1)
|
|
zern_surface = wfe.generate_height_map(zern_rms, 2, 1)
|
|
zern_coefficients = wfe.fit_zernike_coefficients(zern_surface, modes=zernModes)
|
|
zern_coefficients[:3] = 0 # No defocus, tip or tilt
|
|
self.zern_coefficients = wfe.scale_zernike_coefficients(zern_coefficients, zern_rms)
|
|
|
|
# Scale the zernike modes to the clear aperture of the ADC
|
|
self.zern_surface = wfe.zernike_cartesian(self.zern_coefficients, self.xz, self.yz, rescale=False)
|
|
|
|
zern_surface = self.mask_surface(self.zern_surface)#, self.xz, self.yz)
|
|
psd_surface = self.mask_surface(self.psd_surface)#, self.xz, self.yz)
|
|
|
|
self.latest_surface = zern_surface + psd_surface
|
|
return self.latest_surface
|
|
|
|
|
|
def mask_surface(self, A):
|
|
'''
|
|
Set the array values to 0 where
|
|
the radius is > 1, without changing
|
|
the original array.
|
|
'''
|
|
# Rescale x and y
|
|
_mask = np.sqrt(self.xz**2 + self.yz**2) > 1
|
|
B = A.copy()
|
|
B[_mask] = 0
|
|
return B
|
|
|
|
|
|
def scale_wfe(self, zernRMS=20e-5, psdRMS=15e-5):
|
|
'''
|
|
Scaling of the original wfe, given the new
|
|
zernike and psd RMS values that are desired.
|
|
'''
|
|
print('Scaling ADC WFE...')
|
|
_old_psd_rms = np.sqrt(np.mean(self.psd_surface**2))
|
|
_factor_psd = psdRMS / _old_psd_rms
|
|
wfe = WavefrontError(self.n,self.m)
|
|
new_psd_surface = wfe.scale_rms(_factor_psd, data=self.psd_surface)
|
|
|
|
new_zern_coefficients = wfe.scale_zernike_coefficients(self.zern_coefficients, zernRMS)
|
|
|
|
# Scale the zernike modes to the clear aperture of the ADC
|
|
new_zern_surface = wfe.zernike_cartesian(new_zern_coefficients, self.xz, self.yz, rescale=False)
|
|
|
|
new_zern_surface = self.mask_surface(new_zern_surface)#, _xz, _yz)
|
|
new_psd_surface = self.mask_surface(new_psd_surface)#, _xz, _yz)
|
|
|
|
self.latest_surface = new_zern_surface + new_psd_surface
|
|
return self.latest_surface |