aopera 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aopera/__init__.py +51 -0
- aopera/aopsd.py +344 -0
- aopera/control.py +355 -0
- aopera/data/ekarus.ini +45 -0
- aopera/data/harmoni-scao.ini +43 -0
- aopera/data/ohp.ini +13 -0
- aopera/data/papyrus.ini +45 -0
- aopera/data/paranal.ini +13 -0
- aopera/data/sphere.ini +45 -0
- aopera/ffwfs.py +335 -0
- aopera/fiber.py +61 -0
- aopera/otfpsf.py +212 -0
- aopera/photometry.py +219 -0
- aopera/readconfig.py +267 -0
- aopera/shwfs.py +316 -0
- aopera/simulation.py +358 -0
- aopera/trajectory.py +120 -0
- aopera/turbulence.py +445 -0
- aopera/utils.py +142 -0
- aopera/variance.py +112 -0
- aopera/zernike.py +193 -0
- aopera-0.1.0.dist-info/METADATA +741 -0
- aopera-0.1.0.dist-info/RECORD +26 -0
- aopera-0.1.0.dist-info/WHEEL +5 -0
- aopera-0.1.0.dist-info/licenses/LICENSE +674 -0
- aopera-0.1.0.dist-info/top_level.txt +1 -0
aopera/__init__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AOPERA initialization file
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
#%% SETUP LOGGING
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
class COLORS:
|
|
10
|
+
GREEN = "\x1b[32;20m"
|
|
11
|
+
BLUE = "\033[34m"
|
|
12
|
+
YELLOW = "\x1b[33;20m"
|
|
13
|
+
RED = "\x1b[31;20m"
|
|
14
|
+
CYAN = '\033[36m'
|
|
15
|
+
RESET = '\033[0m'
|
|
16
|
+
|
|
17
|
+
log_in_file = False
|
|
18
|
+
loglvl = logging.INFO # set logging.DEBUG to see all internal issues
|
|
19
|
+
|
|
20
|
+
if log_in_file:
|
|
21
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
22
|
+
logfmt = '[%(asctime)s] %(levelname)s in <%(funcName)s> : %(message)s' # %(module)s
|
|
23
|
+
aoerrorpath = os.sep.join(__file__.split(os.sep)[:-2])
|
|
24
|
+
logpath = aoerrorpath + os.sep + 'log'
|
|
25
|
+
if not os.path.isdir(logpath):
|
|
26
|
+
os.mkdir(logpath)
|
|
27
|
+
logging.basicConfig(filename=logpath+os.sep+'default.log', encoding='utf-8',
|
|
28
|
+
level=loglvl, format=logfmt, datefmt=datefmt)
|
|
29
|
+
else:
|
|
30
|
+
logfmt = COLORS.YELLOW + '%(levelname)s' + COLORS.RESET + ' in <%(funcName)s> : %(message)s' # %(module)s
|
|
31
|
+
logging.basicConfig(level=loglvl, format=logfmt)
|
|
32
|
+
|
|
33
|
+
logging.debug('Load <aoerror> library')
|
|
34
|
+
del log_in_file, logfmt, loglvl, os, logging
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
#%% IMPORT MODULES
|
|
38
|
+
from . import utils
|
|
39
|
+
from . import zernike
|
|
40
|
+
from . import readconfig
|
|
41
|
+
from . import variance
|
|
42
|
+
from . import control
|
|
43
|
+
from . import turbulence
|
|
44
|
+
from . import aopsd
|
|
45
|
+
from . import shwfs
|
|
46
|
+
from . import ffwfs
|
|
47
|
+
from . import otfpsf
|
|
48
|
+
from . import photometry
|
|
49
|
+
from . import fiber
|
|
50
|
+
from . import simulation
|
|
51
|
+
from . import trajectory
|
aopera/aopsd.py
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""
|
|
2
|
+
General functions to compute PSD terms of the AO system
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from scipy.special import j1
|
|
7
|
+
from aopera.turbulence import propagation_spherical_coord, vonkarmanshape, phase_psd, cn2dh_to_r0
|
|
8
|
+
from aopera.zernike import zernike_fourier
|
|
9
|
+
from scipy.interpolate import RegularGridInterpolator
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
def piston_filter(ff, D):
|
|
13
|
+
"""Piston filtering function, to be applied on a PSD"""
|
|
14
|
+
ff = np.pi*D*ff
|
|
15
|
+
out = np.zeros_like(ff)
|
|
16
|
+
idx = (ff!=0)
|
|
17
|
+
out[idx] = 1 - (2*j1(ff[idx])/(ff[idx]))**2
|
|
18
|
+
return out
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def aomask_in(fx, fy, aocutoff, shape='circle'):
|
|
22
|
+
"""Mask that is equal to 1 for the corrected frequencies."""
|
|
23
|
+
print(DeprecationWarning('Function `aomask_in` is deprecated, use `controllability`.'))
|
|
24
|
+
if shape=='circle':
|
|
25
|
+
msk = ((fx**2 + fy**2) < aocutoff**2)
|
|
26
|
+
elif shape=='square':
|
|
27
|
+
msk = (np.abs(fx)<aocutoff)*(np.abs(fy)<aocutoff)
|
|
28
|
+
else:
|
|
29
|
+
raise ValueError('Your mask shape is not available')
|
|
30
|
+
return msk
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def aomask_out(*args, **kwargs):
|
|
34
|
+
"""
|
|
35
|
+
Mask that is equal to 1 outside the correction area.
|
|
36
|
+
See `aomask_in` for input arguments.
|
|
37
|
+
"""
|
|
38
|
+
return 1 - aomask_in(*args, **kwargs)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def psd_wvl_scaling(psd_1, w1_over_w2):
|
|
42
|
+
"""
|
|
43
|
+
Scale a PSD [rad²m²] from one wavelength to another.
|
|
44
|
+
The frequecy step verifies df_2 = df_1 * w1_over_w2
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
psd_1 : np.array
|
|
49
|
+
The PSD given at wvl_1
|
|
50
|
+
w1_over_w2 : float
|
|
51
|
+
Ratio wvl_1 / wvl_2
|
|
52
|
+
"""
|
|
53
|
+
nx = psd_1.shape[0]
|
|
54
|
+
xx_1 = np.arange(nx)-nx//2
|
|
55
|
+
xx_2 = np.tile(xx_1 * w1_over_w2, (nx,1))
|
|
56
|
+
interp = RegularGridInterpolator((xx_1,xx_1), psd_1, bounds_error=False, fill_value=0)
|
|
57
|
+
psd_2 = interp((xx_2.T,xx_2)) * w1_over_w2**2
|
|
58
|
+
return psd_2
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def psd_fitting(fx, fy, psdatmo, aocutoff, **kwargs):
|
|
62
|
+
"""
|
|
63
|
+
Return the fitting error of the AO system.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
fx : np.array
|
|
68
|
+
The spatial frequencies on X [1/m].
|
|
69
|
+
fy : np.array
|
|
70
|
+
The spatial frequencies on Y [1/m].
|
|
71
|
+
psdatmo : np.array
|
|
72
|
+
The atmospherical PSD (for example Von-Karman) at the corresponding frequencies.
|
|
73
|
+
aocutoff : float
|
|
74
|
+
The AO cutoff frequency [1/m]
|
|
75
|
+
"""
|
|
76
|
+
return psdatmo * aomask_out(fx, fy, aocutoff, **kwargs)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def psd_aliasing(fx, fy, aocutoff, var, df, **kwargs):
|
|
80
|
+
"""
|
|
81
|
+
Return the aliasing error of the AO system.
|
|
82
|
+
The aliasing PSD is considered as constant on the corrected area.
|
|
83
|
+
|
|
84
|
+
Parameters
|
|
85
|
+
----------
|
|
86
|
+
fx : np.array
|
|
87
|
+
The spatial frequencies on X [1/m].
|
|
88
|
+
fy : np.array
|
|
89
|
+
The spatial frequencies on Y [1/m].
|
|
90
|
+
aocutoff : float
|
|
91
|
+
The AO cutoff frequency [1/m].
|
|
92
|
+
var : float
|
|
93
|
+
The required aliasing variance [rad²].
|
|
94
|
+
df : float
|
|
95
|
+
Frequency step of the `freq` array [1/m].
|
|
96
|
+
"""
|
|
97
|
+
# TODO: remove this function and use dedicated aliasing functions from SH-WFS and FF-WFS
|
|
98
|
+
print(DeprecationWarning('The generic function `psd_aliasing` is deprecated, use aliasing from shwfs or ffwfs instead.'))
|
|
99
|
+
mask = aomask_in(fx, fy, aocutoff, **kwargs)
|
|
100
|
+
f2 = fx**2 + fy**2
|
|
101
|
+
mask[np.where(f2==0)] = 0
|
|
102
|
+
psd = var*mask/(np.sum(mask)*df**2)
|
|
103
|
+
return psd
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def psd_temporal(fx, fy, wspd, psdatmo, cltf, aocutoff=None, wnd_angle=0, **kwargs):
|
|
107
|
+
"""
|
|
108
|
+
Compute servolag error of the AO system.
|
|
109
|
+
Computation is based on input PSD filtered by the ETF.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
fx : np.array
|
|
114
|
+
The spatial frequencies on X [1/m].
|
|
115
|
+
fy : np.array
|
|
116
|
+
The spatial frequencies on Y [1/m].
|
|
117
|
+
wspd : float
|
|
118
|
+
Turbulence equivalent windspeed [m/s].
|
|
119
|
+
psdatmo : np.array
|
|
120
|
+
The turbulent phase PSD [eg. rad2 m2].
|
|
121
|
+
cltf : callable
|
|
122
|
+
Closed-loop transfer function, to be evaluated on temporal frequencies (Hz).
|
|
123
|
+
aocutoff : float
|
|
124
|
+
The AO cutoff frequency [1/m].
|
|
125
|
+
"""
|
|
126
|
+
ft = (fx*np.cos(wnd_angle)+fy*np.sin(wnd_angle))*wspd
|
|
127
|
+
ftnull = np.where(ft==0)
|
|
128
|
+
ft[ftnull] = 1e-8 # avoid numerical issue in f=0
|
|
129
|
+
etf2 = np.abs(cltf(ft))**2.0
|
|
130
|
+
if aocutoff is not None:
|
|
131
|
+
mask = aomask_in(fx, fy, aocutoff, **kwargs)
|
|
132
|
+
else:
|
|
133
|
+
mask = np.ones(fx.shape)
|
|
134
|
+
mask[ftnull] = 0
|
|
135
|
+
return psdatmo*etf2*mask
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def air_refractive_index(wvl_um):
|
|
139
|
+
return 1 + 0.0579/(238.02-wvl_um**(-2)) + 0.0017/(57.4-wvl_um**(-2))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def psd_chromatic_filter(wvl_wfs_nm, wvl_sci_nm):
|
|
143
|
+
n_wfs = air_refractive_index(wvl_wfs_nm*1e-3)
|
|
144
|
+
n_sci = air_refractive_index(wvl_sci_nm*1e-3)
|
|
145
|
+
return (1-(n_sci-1)/(n_wfs-1))**2
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def psd_ncpa(ncpa_rms, freq, df, diameter, ncpa_exp=2.2):
|
|
149
|
+
"""
|
|
150
|
+
Compute the PSD from Non-Common Path Aberrations.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
ncpa_rms : float
|
|
155
|
+
RMS value of NCPA [unit]. Output PSD is given in [unit²m²].
|
|
156
|
+
freq : np.array
|
|
157
|
+
Array of spatial frequencies [1/m].
|
|
158
|
+
df : float
|
|
159
|
+
Frequency step [1/m].
|
|
160
|
+
diameter : float
|
|
161
|
+
Telescope diameter [m].
|
|
162
|
+
"""
|
|
163
|
+
pstflt = piston_filter(freq, diameter)
|
|
164
|
+
ncpa = pstflt / (1e-8 + freq**ncpa_exp)
|
|
165
|
+
ncpa_total = np.sum(ncpa)*df**2
|
|
166
|
+
return ncpa * ncpa_rms**2 / ncpa_total
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def psd_jitter(jitter_x_rms, jitter_y_rms, npix, samp):
|
|
170
|
+
"""
|
|
171
|
+
Compute jitter PSD
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
jitter_x_rms : float
|
|
176
|
+
RMS value [unit] of jitter along X. Output PSD is given in [unit²m²].
|
|
177
|
+
jitter_y_rms : float
|
|
178
|
+
RMS value [unit] of jitter along Y.
|
|
179
|
+
npix : int
|
|
180
|
+
Number of pixels of the output array.
|
|
181
|
+
samp : float
|
|
182
|
+
PSF sampling.
|
|
183
|
+
"""
|
|
184
|
+
zx = np.abs(zernike_fourier(1, -1, npix, samp))**2
|
|
185
|
+
return zx * jitter_x_rms**2 + zx.T * jitter_y_rms**2
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def psd_anisoplanetism(fx, fy, cn2dh, alt, wvl, theta_x, theta_y, lext=np.inf, lint=0, zenith=0):
|
|
189
|
+
"""
|
|
190
|
+
Compute the anisoplanetism PSD, for a source located at infinite distance.
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------
|
|
194
|
+
fx : np.array(float)
|
|
195
|
+
Array of spatial frequencies on the X axis [1/m].
|
|
196
|
+
fy : np.array(float)
|
|
197
|
+
Array of spatial frequencies on the Y axis [1/m].
|
|
198
|
+
cn2dh : list(float)
|
|
199
|
+
List of the cn2*dh of the atmosphere layers [m^(1/3)].
|
|
200
|
+
alt : list(float)
|
|
201
|
+
List of the altitudes corresponding to cn2dh [m].
|
|
202
|
+
The zero altitude is the pupil of the telescope.
|
|
203
|
+
wvl : float
|
|
204
|
+
Wavelength of wavefront [m].
|
|
205
|
+
theta_x : float
|
|
206
|
+
Separation angle along the X coordinate [rad].
|
|
207
|
+
theta_y : float
|
|
208
|
+
Separation angle along the Y coordinate [rad].
|
|
209
|
+
|
|
210
|
+
Reference
|
|
211
|
+
---------
|
|
212
|
+
Rigaut, 1998, SPIE Vol. 3353
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
psd = np.zeros(fx.shape)
|
|
216
|
+
psd_norm = phase_psd(np.sqrt(fx**2+fy**2), 1, lext=lext, lint=lint)
|
|
217
|
+
|
|
218
|
+
for i in range(len(alt)):
|
|
219
|
+
r0 = cn2dh_to_r0([cn2dh[i]], wvl, zenith=zenith)
|
|
220
|
+
psd += psd_norm * r0**(-5/3) * 2 * (1-np.cos(2*np.pi*alt[i]*(theta_x*fx+theta_y*fy)))
|
|
221
|
+
|
|
222
|
+
return psd
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def psd_anisoplanetism_extended_object(fx, fy, cn2dh, alt, dpup, wvl, objsize, src_alt=np.inf, src_zenith=0, lext=np.inf, lint=0, return_all=False):
|
|
226
|
+
"""
|
|
227
|
+
Return the phase, scintillation and coupling anisoplanetism PSD from the WFS measurement on extended object.
|
|
228
|
+
The result is a PSD array in units of rad²m², evaluated on [fx,fy].
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
fx : np.array(float)
|
|
233
|
+
Array of spatial frequencies on the X axis [1/m].
|
|
234
|
+
fy : np.array(float)
|
|
235
|
+
Array of spatial frequencies on the Y axis [1/m].
|
|
236
|
+
cn2dh : list(float)
|
|
237
|
+
List of the cn2*dh of the atmosphere layers [m^(1/3)].
|
|
238
|
+
alt : list(float)
|
|
239
|
+
List of the altitudes corresponding to cn2dh [m].
|
|
240
|
+
The zero altitude is the pupil of the telescope.
|
|
241
|
+
dpup : float
|
|
242
|
+
Size of a subpupil of the WFS [m].
|
|
243
|
+
wvl : float
|
|
244
|
+
Wavelength of wavefront sensing [m].
|
|
245
|
+
objsize : float
|
|
246
|
+
Characterisitic apparent size of a square-like object [rad].
|
|
247
|
+
src_alt : float
|
|
248
|
+
Source altitude [m].
|
|
249
|
+
src_zenith : float
|
|
250
|
+
Source zenital angle [rad].
|
|
251
|
+
A zero angle means a source at zenith.
|
|
252
|
+
lext : float
|
|
253
|
+
Atmospheric turbulence external scale [m].
|
|
254
|
+
lint : float
|
|
255
|
+
Atmospheric turbulence internal scale [m].
|
|
256
|
+
return_all : boolean
|
|
257
|
+
Activate to return all the PSD terms.
|
|
258
|
+
|
|
259
|
+
Note
|
|
260
|
+
----
|
|
261
|
+
Multiply the result by `(wvl_wfs/wvl_sci)**2` to get the PSD at the science wavelength.
|
|
262
|
+
Multiply the result by mask of AO corrected frequencies, see the function `aomask_in`.
|
|
263
|
+
|
|
264
|
+
Reference
|
|
265
|
+
---------
|
|
266
|
+
Vedrenne et al, 2007, JOSAA.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
nlayer = len(cn2dh)
|
|
270
|
+
npix = fx.shape[0]
|
|
271
|
+
k0 = 2*np.pi/wvl
|
|
272
|
+
|
|
273
|
+
freq = np.sqrt(fx**2+fy**2)
|
|
274
|
+
vldx = np.where(fx!=0)
|
|
275
|
+
vldy = np.where(fy!=0)
|
|
276
|
+
|
|
277
|
+
tfpup = np.sinc(fx*dpup)*np.sinc(fy*dpup) # ok with definition: np.sinc(x)=sinc(pi*x)
|
|
278
|
+
|
|
279
|
+
psd = np.zeros((nlayer,npix,npix))
|
|
280
|
+
FF = np.zeros((nlayer,npix,npix))
|
|
281
|
+
GG = np.zeros((nlayer,npix,npix))
|
|
282
|
+
HH = np.zeros((nlayer,npix,npix), dtype=complex)
|
|
283
|
+
Dx = np.zeros((nlayer,npix,npix), dtype=complex)
|
|
284
|
+
Dy = np.zeros((nlayer,npix,npix), dtype=complex)
|
|
285
|
+
EE = np.zeros((nlayer,npix,npix), dtype=complex)
|
|
286
|
+
|
|
287
|
+
xx,yy = np.mgrid[0:npix,0:npix] - npix//2
|
|
288
|
+
|
|
289
|
+
fconv = k0*dpup # facteur de conversion angle vers phase (cf. codes IDL)
|
|
290
|
+
|
|
291
|
+
for i in range(nlayer):
|
|
292
|
+
if (alt[i]>0) and (alt[i]<src_alt):
|
|
293
|
+
ze_z = 1/propagation_spherical_coord(alt[i], src_alt, backward=True)
|
|
294
|
+
ze = alt[i]*ze_z / np.cos(src_zenith)
|
|
295
|
+
u = np.pi*ze*wvl*freq**2
|
|
296
|
+
vk = vonkarmanshape(freq, lext=lext*ze_z, lint=lint*ze_z)
|
|
297
|
+
psd[i,...] = vk * k0**2 *0.033*cn2dh[i]*ze_z**(-5/3) * (2*np.pi)**(-2/3) # [eq A4]*dh
|
|
298
|
+
psd[i,...] = psd[i,...]/np.cos(src_zenith) # zenithal angle effect on cn2dh
|
|
299
|
+
FF[i,...] = 4*psd[i,...] * tfpup**2 * np.sin(u)**2 # [eq A3]
|
|
300
|
+
GG[i,...] = (2*np.pi*dpup)**2 * psd[i,...] * tfpup**2 * np.cos(u)**2 # [eq A7]
|
|
301
|
+
HH[i,...] = 2j*np.pi*dpup * psd[i,...] * tfpup**2 * np.sin(2*u) # [eq A9]/fconv car fconv porté par Dx
|
|
302
|
+
Dx[i,...][vldx] = 1j*fconv*np.sinc(ze*fy[vldx]*objsize)/(2*np.pi*ze*fx[vldx])*(np.cos(np.pi*ze*fx[vldx]*objsize)-np.sinc(ze*fx[vldx]*objsize))
|
|
303
|
+
Dy[i,...][vldy] = 1j*fconv*np.sinc(ze*fx[vldy]*objsize)/(2*np.pi*ze*fy[vldy])*(np.cos(np.pi*ze*fy[vldy]*objsize)-np.sinc(ze*fy[vldy]*objsize))
|
|
304
|
+
EE[i,...] = np.sinc(ze*fx*objsize)*np.sinc(ze*fy*objsize) - 1
|
|
305
|
+
|
|
306
|
+
psd_aa = {}
|
|
307
|
+
psd_aa["xx"] = np.sum(np.conjugate(Dx)*Dx*FF,axis=0)
|
|
308
|
+
psd_aa["xy"] = np.sum(np.conjugate(Dx)*Dy*FF,axis=0)
|
|
309
|
+
psd_aa["yy"] = np.sum(np.conjugate(Dy)*Dy*FF,axis=0)
|
|
310
|
+
|
|
311
|
+
psd_cc = {}
|
|
312
|
+
psd_cc["xx"] = np.sum(GG*np.abs(EE)**2,axis=0)*fx*fx
|
|
313
|
+
psd_cc["xy"] = np.sum(GG*np.abs(EE)**2,axis=0)*fx*fy
|
|
314
|
+
psd_cc["yy"] = np.sum(GG*np.abs(EE)**2,axis=0)*fy*fy
|
|
315
|
+
|
|
316
|
+
psd_acca = {}
|
|
317
|
+
psd_acca["xx"] = np.sum((fx*EE*np.conjugate(Dx)+fx*Dx*np.conjugate(EE))*HH,axis=0)
|
|
318
|
+
psd_acca["xy"] = np.sum((fx*EE*np.conjugate(Dy)+fy*Dx*np.conjugate(EE))*HH,axis=0)
|
|
319
|
+
psd_acca["yy"] = np.sum((fy*EE*np.conjugate(Dy)+fy*Dy*np.conjugate(EE))*HH,axis=0)
|
|
320
|
+
|
|
321
|
+
Mx = 2j*np.pi*fx*tfpup
|
|
322
|
+
My = 2j*np.pi*fy*tfpup
|
|
323
|
+
|
|
324
|
+
logging.debug('Measurement anisoplanetism has been scaled to match IDL codes.')
|
|
325
|
+
Mx *= dpup * np.pi/2 #FIXME : factor to match IDL codes
|
|
326
|
+
My *= dpup * np.pi/2 #FIXME : factor to match IDL codes
|
|
327
|
+
|
|
328
|
+
def reconstructor(psd):
|
|
329
|
+
"""Slope PSD to phase PSD [eq B5]"""
|
|
330
|
+
denom = np.abs(Mx)**4 + np.abs(My)**4
|
|
331
|
+
vld = np.where(denom>0)
|
|
332
|
+
not_vld = np.where(denom==0)
|
|
333
|
+
psd_wfe = np.real(psd["xx"]*np.abs(Mx)**2 + psd["yy"]*np.abs(My)**2 + 2*np.conjugate(Mx)*My*psd["xy"])
|
|
334
|
+
psd_wfe[vld] /= denom[vld]
|
|
335
|
+
psd_wfe[not_vld] = 0
|
|
336
|
+
return psd_wfe
|
|
337
|
+
|
|
338
|
+
psd_wfe_aa = reconstructor(psd_aa)
|
|
339
|
+
psd_wfe_cc = reconstructor(psd_cc)
|
|
340
|
+
psd_wfe_acca = reconstructor(psd_acca)
|
|
341
|
+
psd_wfe_tot = psd_wfe_aa + psd_wfe_cc + psd_wfe_acca
|
|
342
|
+
if return_all:
|
|
343
|
+
return psd_wfe_tot, psd_wfe_aa, psd_wfe_cc, psd_wfe_acca
|
|
344
|
+
return psd_wfe_tot
|