Files
fourierOptics/fourierOptics/micado.py
T

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