AnisoCADO 0.3.0__py3-none-any.whl → 0.4.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.
Potentially problematic release.
This version of AnisoCADO might be problematic. Click here for more details.
- anisocado/__init__.py +10 -1
- anisocado/_anisocado.py +41 -49
- anisocado/misc.py +34 -24
- anisocado/psf.py +65 -48
- anisocado/psf_utils.py +35 -58
- anisocado/pupil_utils.py +210 -159
- anisocado/tests/__init__.py +1 -0
- anisocado/tests/test_playing_around.py +62 -0
- anisocado/tests/test_psf_functions.py +100 -0
- anisocado/tests/test_scao_psf.py +89 -0
- anisocado-0.4.0.dist-info/METADATA +53 -0
- anisocado-0.4.0.dist-info/RECORD +14 -0
- {AnisoCADO-0.3.0.dist-info → anisocado-0.4.0.dist-info}/WHEEL +1 -2
- AnisoCADO-0.3.0.dist-info/METADATA +0 -52
- AnisoCADO-0.3.0.dist-info/RECORD +0 -12
- AnisoCADO-0.3.0.dist-info/top_level.txt +0 -1
- anisocado/version.py +0 -3
- {AnisoCADO-0.3.0.dist-info → anisocado-0.4.0.dist-info}/LICENSE +0 -0
anisocado/psf_utils.py
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Originally from file _anisocado.py.
|
|
2
3
|
|
|
3
|
-
import numpy as np
|
|
4
|
-
from . import pupil_utils
|
|
5
|
-
|
|
6
|
-
# ____ _____ _ ____ __ __ _____
|
|
7
|
-
# | _ \| ____| / \ | _ \| \/ | ____|
|
|
8
|
-
# | |_) | _| / _ \ | | | | |\/| | _|
|
|
9
|
-
# | _ <| |___ / ___ \| |_| | | | | |___
|
|
10
|
-
# |_| \_\_____/_/ \_\____/|_| |_|_____|
|
|
11
|
-
|
|
12
|
-
"""
|
|
13
|
-
Hello.
|
|
14
4
|
This files contains some useful functions related to psf generation.
|
|
15
5
|
They are just raw, so that you can insert them into your own classes
|
|
16
6
|
as desired.
|
|
@@ -23,6 +13,9 @@ It contains examples that will show you how to use the functions, what they do,
|
|
|
23
13
|
etc.
|
|
24
14
|
"""
|
|
25
15
|
|
|
16
|
+
import numpy as np
|
|
17
|
+
from . import pupil_utils
|
|
18
|
+
|
|
26
19
|
|
|
27
20
|
def defineDmFrequencyArea(kx, ky, rotdegree, dactu=0.5403):
|
|
28
21
|
"""
|
|
@@ -46,19 +39,18 @@ def defineDmFrequencyArea(kx, ky, rotdegree, dactu=0.5403):
|
|
|
46
39
|
wavelength = 1.65e-6 # metres
|
|
47
40
|
kx, ky = computeSpatialFreqArrays(N, pixelSize, wavelength)
|
|
48
41
|
M4 = defineDmFrequencyArea(kx, ky, 0)
|
|
49
|
-
plt.imshow(np.fft.fftshift(M4).T, origin=
|
|
42
|
+
plt.imshow(np.fft.fftshift(M4).T, origin="l")
|
|
50
43
|
|
|
51
44
|
"""
|
|
52
|
-
|
|
53
45
|
# cut-off frequency
|
|
54
46
|
fc = (1./np.sqrt(3)) / dactu
|
|
55
47
|
|
|
56
48
|
# mask frequency definition
|
|
57
49
|
A = np.pi/3 # 60 degrees
|
|
58
50
|
A0 = rotdegree * np.pi / 180
|
|
59
|
-
msk = np.abs(np.cos(A0)*ky+np.sin(A0)*kx)<fc
|
|
60
|
-
msk = np.logical_and(msk, np.abs(np.cos(A+A0)*ky+np.sin(A+A0)*kx)<fc)
|
|
61
|
-
msk = np.logical_and(msk, np.abs(np.cos(2*A+A0)*ky+np.sin(2*A+A0)*kx)<fc)
|
|
51
|
+
msk = np.abs(np.cos(A0)*ky+np.sin(A0)*kx) < fc
|
|
52
|
+
msk = np.logical_and(msk, np.abs(np.cos(A+A0)*ky+np.sin(A+A0)*kx) < fc)
|
|
53
|
+
msk = np.logical_and(msk, np.abs(np.cos(2*A+A0)*ky+np.sin(2*A+A0)*kx) < fc)
|
|
62
54
|
k = np.sqrt(kx**2 + ky**2)
|
|
63
55
|
msk = np.logical_and(msk, k < (fc*1.115))
|
|
64
56
|
|
|
@@ -85,7 +77,6 @@ def computeSpatialFreqArrays(N, pixelSize, wavelength):
|
|
|
85
77
|
kx, ky, uk = computeSpatialFreqArrays(N, pixelSize, wavelength)
|
|
86
78
|
|
|
87
79
|
"""
|
|
88
|
-
|
|
89
80
|
# array of indices centred 'as expected' by Fourier frequencies, in 1D
|
|
90
81
|
k1d = np.fft.fftshift(np.arange(N) - (N//2))
|
|
91
82
|
|
|
@@ -97,7 +88,7 @@ def computeSpatialFreqArrays(N, pixelSize, wavelength):
|
|
|
97
88
|
k1d = k1d * uk # now this is a spatial freq in metres^-1
|
|
98
89
|
|
|
99
90
|
# now creating 2D arrays of spatial frequency
|
|
100
|
-
kx, ky = np.meshgrid(k1d, k1d, indexing=
|
|
91
|
+
kx, ky = np.meshgrid(k1d, k1d, indexing="ij") # for convention [x,y]
|
|
101
92
|
|
|
102
93
|
return kx, ky, uk
|
|
103
94
|
|
|
@@ -126,7 +117,6 @@ def computeWiener(kx, ky, L0, r0):
|
|
|
126
117
|
W = computeWiener(kx, ky, L0, r0)
|
|
127
118
|
|
|
128
119
|
"""
|
|
129
|
-
|
|
130
120
|
# computation of Wiener spectrum expressed in radians^2 (at the wavelength
|
|
131
121
|
# where r0(lambda) is expressed !)
|
|
132
122
|
Wiener = (kx**2 + ky**2 + 1./L0**2.)**(-11./6)
|
|
@@ -178,7 +168,6 @@ def anisoplanaticSpectrum(Cn2h, layerAltitude, L0, offx, offy, wavelength,
|
|
|
178
168
|
wavelength, kx, ky, W, M4)
|
|
179
169
|
|
|
180
170
|
"""
|
|
181
|
-
|
|
182
171
|
# number of turbulent layers involved in that computation
|
|
183
172
|
nlayers = len(Cn2h)
|
|
184
173
|
|
|
@@ -190,10 +179,11 @@ def anisoplanaticSpectrum(Cn2h, layerAltitude, L0, offx, offy, wavelength,
|
|
|
190
179
|
|
|
191
180
|
# loop over turbulent layers, summing transfer function of each layer
|
|
192
181
|
for i in range(nlayers):
|
|
193
|
-
|
|
194
|
-
|
|
182
|
+
# shift in metres on the layer in X
|
|
183
|
+
dx = layerAltitude[i] * offx / RASC
|
|
184
|
+
dy = layerAltitude[i] * offy / RASC # idem, in Y
|
|
195
185
|
tmp = (2j*np.pi*dx)*kx + (2j*np.pi*dy)*ky
|
|
196
|
-
Haniso += Cn2h[i] * np.abs(1 - np.exp(
|
|
186
|
+
Haniso += Cn2h[i] * np.abs(1 - np.exp(tmp))**2
|
|
197
187
|
|
|
198
188
|
# now applying the transfer function on the Wiener spectrum, only in the
|
|
199
189
|
# spatial frequency range of M4
|
|
@@ -228,9 +218,8 @@ def fittingSpectrum(Wiener, M4):
|
|
|
228
218
|
f = fittingSpectrum(W, M4)
|
|
229
219
|
|
|
230
220
|
"""
|
|
231
|
-
|
|
232
221
|
Wfit = Wiener.copy()
|
|
233
|
-
Wfit[M4] = 0.0
|
|
222
|
+
Wfit[M4] = 0.0 # M4 cancels whatever is in its compensation domain
|
|
234
223
|
return Wfit
|
|
235
224
|
|
|
236
225
|
|
|
@@ -255,7 +244,6 @@ def otherSpectrum(nmRms, M4, uk, wavelength):
|
|
|
255
244
|
f = otherSpectrum(nmRms, M4, uk, wavelength)
|
|
256
245
|
|
|
257
246
|
"""
|
|
258
|
-
|
|
259
247
|
fact = 2 * np.pi * nmRms * 1e-9 / uk / wavelength
|
|
260
248
|
fact = fact**2
|
|
261
249
|
tot = np.sum(M4)
|
|
@@ -280,12 +268,11 @@ def aliasingSpectrum(kx, ky, r0, L0, M4, dssp=0.4015):
|
|
|
280
268
|
W = aliasingSpectrum(kx, ky, r0, L0, M4)
|
|
281
269
|
|
|
282
270
|
"""
|
|
283
|
-
|
|
284
271
|
ke = 1.0 / dssp # computes the sampling spatial-frequency of the WFS
|
|
285
272
|
|
|
286
273
|
kxt = kx[M4]
|
|
287
274
|
kyt = ky[M4]
|
|
288
|
-
Wt
|
|
275
|
+
Wt = ((kxt-ke)**2 + kyt**2 + 1./L0**2.)**(-11./6)
|
|
289
276
|
Wt += ((kxt+ke)**2 + kyt**2 + 1./L0**2.)**(-11./6)
|
|
290
277
|
Wt += (kxt**2 + (kyt-ke)**2 + 1./L0**2.)**(-11./6)
|
|
291
278
|
Wt += (kxt**2 + (kyt+ke)**2 + 1./L0**2.)**(-11./6)
|
|
@@ -317,9 +304,9 @@ def computeBpSpectrum(kx, ky, V, Fe, tret, gain, Wiener, M4):
|
|
|
317
304
|
f = computeBpSpectrum(kx, ky, V, Fe, tret, gain, W, M4)
|
|
318
305
|
|
|
319
306
|
"""
|
|
320
|
-
|
|
321
307
|
k = np.sqrt(kx*kx + ky*ky)
|
|
322
|
-
|
|
308
|
+
# pourquoi un sqrt(2) ? je ne saurais dire ...!!!
|
|
309
|
+
nu = k * V / np.sqrt(2)
|
|
323
310
|
Wbp = hcor(nu, Fe, tret, gain, 500) * Wiener
|
|
324
311
|
Wbp[np.logical_not(M4)] = 0.
|
|
325
312
|
return Wbp
|
|
@@ -337,17 +324,16 @@ def hcor(freq, Fe, tret, G, BP, an=True):
|
|
|
337
324
|
end of the integration and the start of the command.
|
|
338
325
|
|
|
339
326
|
"""
|
|
340
|
-
|
|
341
327
|
Te = 1. / Fe
|
|
342
328
|
p = 1j * 2 * np.pi * freq + 1e-12
|
|
343
329
|
|
|
344
|
-
Hint = 1./(1-np.exp(-p*Te))
|
|
330
|
+
Hint = 1./(1-np.exp(-p*Te)) # numeric integrator
|
|
345
331
|
Hccd = (1.-np.exp(-p*Te))/(p*Te) # echant bloqueur avec retard 1/2 trame
|
|
346
332
|
Hdac = Hccd # echant bloqueur avec retard 1/2 trame
|
|
347
333
|
Hret = np.exp(-p*tret)
|
|
348
334
|
Hmir = 1./(1. + 1j*freq/BP)
|
|
349
335
|
Hbo = Hint * Hccd * Hdac * Hret * Hmir
|
|
350
|
-
Hcor
|
|
336
|
+
Hcor = 1./abs(1 + Hbo*G)**2
|
|
351
337
|
|
|
352
338
|
return Hcor
|
|
353
339
|
|
|
@@ -362,14 +348,14 @@ def convertSpectrum2Dphi(W, uk):
|
|
|
362
348
|
Uses Dphi(r) = $ $ (1-cos(2.pi.k.r)) W(k) d2k
|
|
363
349
|
Computation of Dphi is in radians^2 at the wavelength of r0.
|
|
364
350
|
"""
|
|
365
|
-
|
|
366
351
|
W[0, 0] = 0.0
|
|
367
352
|
W[0, 0] = -np.sum(W)
|
|
368
|
-
Dphi = 2*np.abs(np.fft.fft2(W)) *
|
|
353
|
+
Dphi = 2*np.abs(np.fft.fft2(W)) * (uk**2)
|
|
369
354
|
return Dphi
|
|
370
355
|
|
|
371
356
|
|
|
372
|
-
def fake_generatePupil(N, deadSegments, rotdegree, pixelSize, wavelength,
|
|
357
|
+
def fake_generatePupil(N, deadSegments, rotdegree, pixelSize, wavelength,
|
|
358
|
+
rng=np.random.default_rng()):
|
|
373
359
|
"""
|
|
374
360
|
<N> : size of the output image, that is made to match the size
|
|
375
361
|
of the (square) psf image to be processed. In other
|
|
@@ -414,11 +400,9 @@ def fake_generatePupil(N, deadSegments, rotdegree, pixelSize, wavelength, rng=np
|
|
|
414
400
|
|
|
415
401
|
|
|
416
402
|
def computeEeltOTF(pup):
|
|
417
|
-
"""
|
|
418
|
-
"""
|
|
419
|
-
# Computation of telescope OTF
|
|
403
|
+
"""Compute the telescope OTF."""
|
|
420
404
|
Nx, Ny = pup.shape
|
|
421
|
-
FTOtel = np.fft.fft2(
|
|
405
|
+
FTOtel = np.fft.fft2(np.abs(np.fft.fft2(pup))**2).real
|
|
422
406
|
FTOtel /= np.sum(pup)**2 * Nx * Ny
|
|
423
407
|
return FTOtel
|
|
424
408
|
|
|
@@ -472,12 +456,11 @@ def core_generatePsf(Dphi, FTOtel):
|
|
|
472
456
|
plt.imshow( psf[N//2-window:N//2+window, N//2-window:N//2+window]**0.3 )
|
|
473
457
|
|
|
474
458
|
"""
|
|
475
|
-
|
|
476
459
|
# total FTO
|
|
477
460
|
FTO = np.exp(-0.5*Dphi) * FTOtel
|
|
478
461
|
|
|
479
462
|
# PSF
|
|
480
|
-
psf = np.fft.fftshift(
|
|
463
|
+
psf = np.fft.fftshift(np.fft.fft2(FTO).real)
|
|
481
464
|
return psf
|
|
482
465
|
|
|
483
466
|
|
|
@@ -510,7 +493,6 @@ def createAdHocScaoPsf(N, pixelSize, wavelengthIR, rotdegree, r0Vis, nmRms):
|
|
|
510
493
|
r0Vis, nmRms)
|
|
511
494
|
|
|
512
495
|
"""
|
|
513
|
-
|
|
514
496
|
# let's compute r0 in the IR using the
|
|
515
497
|
# r0 chromatic translation formula
|
|
516
498
|
wavelengthVis = 500e-9
|
|
@@ -566,14 +548,13 @@ def airmassImpact(r0_at_zenith, zenith_distance):
|
|
|
566
548
|
distance. This function converts a r0 given at zenith into the real r0
|
|
567
549
|
actually observed by the telescope.
|
|
568
550
|
"""
|
|
569
|
-
|
|
570
551
|
z = zenith_distance * np.pi / 180 # the same, in radians
|
|
571
552
|
r0 = r0_at_zenith * np.cos(z)**(3./5)
|
|
572
553
|
|
|
573
554
|
return r0
|
|
574
555
|
|
|
575
556
|
|
|
576
|
-
def get_atmospheric_turbulence(myProfile=
|
|
557
|
+
def get_atmospheric_turbulence(myProfile="EsoMedian"):
|
|
577
558
|
"""
|
|
578
559
|
Returns the relative level of turbulence at a given height
|
|
579
560
|
|
|
@@ -614,7 +595,7 @@ def get_atmospheric_turbulence(myProfile='EsoMedian'):
|
|
|
614
595
|
Parameters
|
|
615
596
|
----------
|
|
616
597
|
myProfile : str, optional
|
|
617
|
-
Profile name: [
|
|
598
|
+
Profile name: ["EsoQ1", "EsoMedian", "EsoQ4", "oldEso", "gendron"]
|
|
618
599
|
|
|
619
600
|
Returns
|
|
620
601
|
-------
|
|
@@ -624,15 +605,14 @@ def get_atmospheric_turbulence(myProfile='EsoMedian'):
|
|
|
624
605
|
Relative strength of turbulence
|
|
625
606
|
|
|
626
607
|
"""
|
|
627
|
-
|
|
628
608
|
layerAltitude, Cn2h = [], []
|
|
629
609
|
|
|
630
|
-
if myProfile ==
|
|
610
|
+
if myProfile == "oldEso":
|
|
631
611
|
layerAltitude = [47., 140, 281, 562, 1125, 2250, 4500, 9000, 18000.]
|
|
632
612
|
Cn2h = [0.5224, 0.026, 0.0444, 0.116, 0.0989,
|
|
633
613
|
0.0295, 0.0598, 0.043, 0.06]
|
|
634
614
|
|
|
635
|
-
elif myProfile ==
|
|
615
|
+
elif myProfile == "officialEsoMedian" or myProfile == "EsoMedian":
|
|
636
616
|
layerAltitude = [30, 90, 150, 200, 245, 300, 390, 600, 1130, 1880, 2630,
|
|
637
617
|
3500, 4500, 5500, 6500, 7500, 8500, 9500, 10500, 11500,
|
|
638
618
|
12500, 13500, 14500, 15500, 16500, 17500, 18500, 19500,
|
|
@@ -644,7 +624,7 @@ def get_atmospheric_turbulence(myProfile='EsoMedian'):
|
|
|
644
624
|
Cn2h = np.array(Cn2h)
|
|
645
625
|
Cn2h /= np.sum(Cn2h)
|
|
646
626
|
|
|
647
|
-
elif myProfile ==
|
|
627
|
+
elif myProfile == "EsoQ1":
|
|
648
628
|
layerAltitude = [30, 90, 150, 200, 245, 300, 390, 600, 1130, 1880, 2630,
|
|
649
629
|
3500, 4500, 5500, 6500, 7500, 8500, 9500, 10500, 11500,
|
|
650
630
|
12500, 13500, 14500, 15500, 16500, 17500, 18500, 19500,
|
|
@@ -656,7 +636,7 @@ def get_atmospheric_turbulence(myProfile='EsoMedian'):
|
|
|
656
636
|
Cn2h = np.array(Cn2h)
|
|
657
637
|
Cn2h /= np.sum(Cn2h)
|
|
658
638
|
|
|
659
|
-
elif myProfile ==
|
|
639
|
+
elif myProfile == "EsoQ4":
|
|
660
640
|
layerAltitude = [30, 90, 150, 200, 245, 300, 390, 600, 1130, 1880, 2630,
|
|
661
641
|
3500, 4500, 5500, 6500, 7500, 8500, 9500, 10500, 11500,
|
|
662
642
|
12500, 13500, 14500, 15500, 16500, 17500, 18500, 19500,
|
|
@@ -668,7 +648,7 @@ def get_atmospheric_turbulence(myProfile='EsoMedian'):
|
|
|
668
648
|
Cn2h = np.array(Cn2h)
|
|
669
649
|
Cn2h /= np.sum(Cn2h)
|
|
670
650
|
|
|
671
|
-
elif myProfile ==
|
|
651
|
+
elif myProfile == "gendron":
|
|
672
652
|
Cn2h = [1.0]
|
|
673
653
|
layerAltitude = [4414.]
|
|
674
654
|
|
|
@@ -697,11 +677,11 @@ def get_profile_defaults(myProfile="EsoMedian"):
|
|
|
697
677
|
seeing = 0.67
|
|
698
678
|
zen_dist = 30
|
|
699
679
|
wind_alpha = 1.
|
|
700
|
-
if myProfile ==
|
|
680
|
+
if myProfile == "EsoQ1":
|
|
701
681
|
seeing = 0.4
|
|
702
682
|
zen_dist = 0
|
|
703
683
|
wind_alpha = 0.88
|
|
704
|
-
elif myProfile ==
|
|
684
|
+
elif myProfile == "EsoQ4":
|
|
705
685
|
seeing = 1.0
|
|
706
686
|
zen_dist = 60
|
|
707
687
|
wind_alpha = 1.3
|
|
@@ -728,6 +708,3 @@ def round_edges(kernel, edge_width=10):
|
|
|
728
708
|
kernel[:, -n:] *= falloff
|
|
729
709
|
|
|
730
710
|
return kernel
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|