AnisoCADO 0.2.3__tar.gz → 0.4.0__tar.gz
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-0.4.0/PKG-INFO +53 -0
- {AnisoCADO-0.2.3 → anisocado-0.4.0}/README.md +6 -3
- anisocado-0.4.0/anisocado/__init__.py +11 -0
- {AnisoCADO-0.2.3 → anisocado-0.4.0}/anisocado/_anisocado.py +41 -49
- {AnisoCADO-0.2.3 → anisocado-0.4.0}/anisocado/misc.py +34 -24
- {AnisoCADO-0.2.3 → anisocado-0.4.0}/anisocado/psf.py +65 -48
- {AnisoCADO-0.2.3 → anisocado-0.4.0}/anisocado/psf_utils.py +35 -58
- {AnisoCADO-0.2.3 → anisocado-0.4.0}/anisocado/pupil_utils.py +210 -159
- anisocado-0.4.0/anisocado/tests/test_playing_around.py +62 -0
- anisocado-0.4.0/anisocado/tests/test_psf_functions.py +100 -0
- anisocado-0.4.0/anisocado/tests/test_scao_psf.py +89 -0
- anisocado-0.4.0/pyproject.toml +65 -0
- AnisoCADO-0.2.3/AnisoCADO.egg-info/PKG-INFO +0 -42
- AnisoCADO-0.2.3/AnisoCADO.egg-info/SOURCES.txt +0 -16
- AnisoCADO-0.2.3/AnisoCADO.egg-info/requires.txt +0 -16
- AnisoCADO-0.2.3/AnisoCADO.egg-info/top_level.txt +0 -1
- AnisoCADO-0.2.3/MANIFEST.in +0 -2
- AnisoCADO-0.2.3/PKG-INFO +0 -42
- AnisoCADO-0.2.3/anisocado/__init__.py +0 -2
- AnisoCADO-0.2.3/anisocado/version.py +0 -3
- AnisoCADO-0.2.3/pyproject.toml +0 -53
- AnisoCADO-0.2.3/setup.cfg +0 -4
- {AnisoCADO-0.2.3 → anisocado-0.4.0}/LICENSE +0 -0
- /AnisoCADO-0.2.3/AnisoCADO.egg-info/dependency_links.txt → /anisocado-0.4.0/anisocado/tests/__init__.py +0 -0
anisocado-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: AnisoCADO
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Generate off-axis SCAO PSFs for the ELT
|
|
5
|
+
License: GPL-3.0-or-later
|
|
6
|
+
Keywords: astronomy
|
|
7
|
+
Author: Kieran Leschinski
|
|
8
|
+
Author-email: kieran.leschinski@unive.ac.at
|
|
9
|
+
Maintainer: Kieran Leschinski
|
|
10
|
+
Maintainer-email: kieran.leschinski@unive.ac.at
|
|
11
|
+
Requires-Python: >=3.10,<3.14
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
21
|
+
Requires-Dist: astropy (>=6.1.7,<8.0.0)
|
|
22
|
+
Requires-Dist: matplotlib (>=3.10.1,<4.0.0)
|
|
23
|
+
Requires-Dist: numpy (>=1.26.4,<2.3.0) ; python_version >= "3.10" and python_version < "3.13"
|
|
24
|
+
Requires-Dist: numpy (>=2.2.6,<3.0.0) ; python_version >= "3.13"
|
|
25
|
+
Project-URL: Bug Tracker, https://github.com/AstarVienna/AnisoCADO/issues
|
|
26
|
+
Project-URL: Changelog, https://github.com/AstarVienna/AnisoCADO/releases
|
|
27
|
+
Project-URL: Documentation, https://anisocado.readthedocs.io/en/latest/
|
|
28
|
+
Project-URL: Repository, https://github.com/AstarVienna/AnisoCADO/
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# AnisoCADO
|
|
32
|
+
|
|
33
|
+
[](https://github.com/AstarVienna/AnisoCADO/actions/workflows/tests.yml)
|
|
34
|
+
[](https://anisocado.readthedocs.io/en/latest/?badge=latest)
|
|
35
|
+
[](https://python-poetry.org/)
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
39
|
+

|
|
40
|
+
[](https://pypi.org/project/AnisoCADO/)
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
A python package to generate off-axis PSFs for the SCAO mode for the ELT
|
|
45
|
+
|
|
46
|
+
Please note: this package is not yet finished yet! The code is fine, but the
|
|
47
|
+
documentation is lacking.
|
|
48
|
+
|
|
49
|
+
## Documentation
|
|
50
|
+
|
|
51
|
+
Apropos documentation. It can be found here:
|
|
52
|
+
[https://anisocado.readthedocs.io/en/latest/index.html](https://anisocado.readthedocs.io/en/latest/index.html)
|
|
53
|
+
|
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/AstarVienna/AnisoCADO/actions/workflows/tests.yml)
|
|
4
4
|
[](https://anisocado.readthedocs.io/en/latest/?badge=latest)
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
[](https://python-poetry.org/)
|
|
6
|
+

|
|
7
7
|
|
|
8
8
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
9
|
+

|
|
10
|
+
[](https://pypi.org/project/AnisoCADO/)
|
|
11
|
+

|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
A python package to generate off-axis PSFs for the SCAO mode for the ELT
|
|
12
15
|
|
|
13
|
-
Please note: this package is not yet finished yet! The code is fine, but the
|
|
16
|
+
Please note: this package is not yet finished yet! The code is fine, but the
|
|
14
17
|
documentation is lacking.
|
|
15
18
|
|
|
16
19
|
## Documentation
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .psf import AnalyticalScaoPsf
|
|
2
|
+
from .misc import field_positions_for_simcado_psf, make_simcado_psf_file
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from importlib import metadata
|
|
6
|
+
from packaging.version import parse
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
__version__ = parse(metadata.version(__package__))
|
|
10
|
+
except metadata.PackageNotFoundError:
|
|
11
|
+
__version__ = "undetermined"
|
|
@@ -1,28 +1,43 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
1
|
# -*- coding: utf-8 -*-
|
|
3
2
|
"""
|
|
3
|
+
Main module.
|
|
4
|
+
|
|
4
5
|
Created on Tue Mar 12 18:48:47 2019
|
|
5
6
|
|
|
6
7
|
@author: Eric Gendron
|
|
7
|
-
Edited by Kieran Leschinski
|
|
8
|
+
Edited by Kieran Leschinski and Fabian Haberhauer
|
|
8
9
|
"""
|
|
9
10
|
|
|
11
|
+
import numpy as np
|
|
10
12
|
import matplotlib.pyplot as plt
|
|
11
|
-
from anisocado.psf_utils import *
|
|
12
|
-
from anisocado.psf_utils import get_atmospheric_turbulence
|
|
13
|
-
|
|
14
|
-
# _ _ ____ _
|
|
15
|
-
# | | | |___ ___ / ___|__ _ ___ ___ / |
|
|
16
|
-
# | | | / __|/ _ \ | | / _` / __|/ _ \ | |
|
|
17
|
-
# | |_| \__ \ __/ | |__| (_| \__ \ __/ | |
|
|
18
|
-
# \___/|___/\___| \____\__,_|___/\___| |_|
|
|
19
13
|
|
|
14
|
+
from anisocado.psf_utils import (
|
|
15
|
+
get_atmospheric_turbulence,
|
|
16
|
+
computeEeltOTF,
|
|
17
|
+
computeSpatialFreqArrays,
|
|
18
|
+
createAdHocScaoPsf,
|
|
19
|
+
anisoplanaticSpectrum,
|
|
20
|
+
convertSpectrum2Dphi,
|
|
21
|
+
defineDmFrequencyArea,
|
|
22
|
+
computeBpSpectrum,
|
|
23
|
+
otherSpectrum,
|
|
24
|
+
airmassImpact,
|
|
25
|
+
fittingSpectrum,
|
|
26
|
+
aliasingSpectrum,
|
|
27
|
+
computeWiener,
|
|
28
|
+
r0Converter,
|
|
29
|
+
fake_generatePupil,
|
|
30
|
+
core_generatePsf,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# use-case 1
|
|
20
35
|
|
|
21
36
|
def shift_scao_psf(plots=False):
|
|
22
37
|
"""
|
|
23
38
|
Problem:
|
|
24
39
|
You have an on-axis PSF.
|
|
25
|
-
You want to
|
|
40
|
+
You want to "move" it off-axis, let's say (+15, +20) arcsec.
|
|
26
41
|
|
|
27
42
|
|
|
28
43
|
For that, you will need to know:
|
|
@@ -31,7 +46,6 @@ def shift_scao_psf(plots=False):
|
|
|
31
46
|
- the global L0
|
|
32
47
|
|
|
33
48
|
"""
|
|
34
|
-
###########
|
|
35
49
|
# Setup
|
|
36
50
|
|
|
37
51
|
# Let's take an example. I create a PSF, that could be coming from
|
|
@@ -50,10 +64,10 @@ def shift_scao_psf(plots=False):
|
|
|
50
64
|
|
|
51
65
|
if plots:
|
|
52
66
|
# I can even look at it:
|
|
53
|
-
plt.imshow(psf.T, origin=
|
|
54
|
-
print(
|
|
67
|
+
plt.imshow(psf.T, origin="l")
|
|
68
|
+
print("Strehl ratio of initial psf is ", psf.max())
|
|
55
69
|
|
|
56
|
-
# OK. That's the starting point
|
|
70
|
+
# OK. That's the starting point.
|
|
57
71
|
|
|
58
72
|
# Now I need to know the atmospheric properties, in particular the Cn2h
|
|
59
73
|
# profile. Let me offer you a little selection of atmospheric profiles.
|
|
@@ -74,9 +88,8 @@ def shift_scao_psf(plots=False):
|
|
|
74
88
|
# in arcsecs (this one is for those who'd like to check nothing will change
|
|
75
89
|
# at the end)
|
|
76
90
|
# offx, offy = (0, 0)
|
|
77
|
-
offx, offy = (0., 16.)
|
|
91
|
+
offx, offy = (0., 16.) # [arcsec]
|
|
78
92
|
|
|
79
|
-
####################
|
|
80
93
|
# Generate PSF
|
|
81
94
|
|
|
82
95
|
# Then let's start the work. I will create spatial frequency arrays.
|
|
@@ -104,21 +117,16 @@ def shift_scao_psf(plots=False):
|
|
|
104
117
|
|
|
105
118
|
psf_aniso = core_generatePsf(Dphi, fto)
|
|
106
119
|
|
|
107
|
-
print(
|
|
108
|
-
plt.imshow(psf_aniso.T, origin=
|
|
120
|
+
print("Strehl off-axis is", psf_aniso.max())
|
|
121
|
+
plt.imshow(psf_aniso.T, origin="lower")
|
|
109
122
|
|
|
110
123
|
return psf_aniso
|
|
111
124
|
|
|
112
|
-
# _ _ ____ ____
|
|
113
|
-
# | | | |___ ___ / ___|__ _ ___ ___ |___ \
|
|
114
|
-
# | | | / __|/ _ \ | | / _` / __|/ _ \ __) |
|
|
115
|
-
# | |_| \__ \ __/ | |__| (_| \__ \ __/ / __/
|
|
116
|
-
# \___/|___/\___| \____\__,_|___/\___| |_____|
|
|
117
125
|
|
|
126
|
+
# use-case 2
|
|
118
127
|
|
|
119
128
|
def exnihilo_scao_psf():
|
|
120
129
|
"""
|
|
121
|
-
|
|
122
130
|
You want to generate SCAO PSFs ex-nihilo, using a simple, approximative
|
|
123
131
|
simulation software.
|
|
124
132
|
You are aware that the simulated PSFs will be perfectly smooth (infinitely
|
|
@@ -128,8 +136,6 @@ def exnihilo_scao_psf():
|
|
|
128
136
|
For this you need to know all the parameters of the simulation.
|
|
129
137
|
|
|
130
138
|
"""
|
|
131
|
-
|
|
132
|
-
###########
|
|
133
139
|
# Setup
|
|
134
140
|
|
|
135
141
|
# nb of pixels of the image to be simulated.
|
|
@@ -152,7 +158,8 @@ def exnihilo_scao_psf():
|
|
|
152
158
|
L0 = 25. # 25 metres is the Armazones median value
|
|
153
159
|
seeing = 0.8 # in arcseconds
|
|
154
160
|
r0Vis = 0.103 / seeing # r0Vis is in metres here, 0.103 is in metres.arcsec
|
|
155
|
-
|
|
161
|
+
# convert r0 at 500nm to IR
|
|
162
|
+
r0IR = r0Converter(r0Vis, 500e-9, wavelengthIR)
|
|
156
163
|
|
|
157
164
|
# Just to use that wonderful function, i decide that the seeing given here
|
|
158
165
|
# was expressed at zenith, while our telescope observes at 30° from zenith.
|
|
@@ -161,7 +168,8 @@ def exnihilo_scao_psf():
|
|
|
161
168
|
# telescope, so that their apparent distance grows with airmass --> this is
|
|
162
169
|
# very bad for anisoplanatism !..
|
|
163
170
|
zenDist = 30. # I observe at 30° from zenith
|
|
164
|
-
|
|
171
|
+
# apparent seeing degrades with airmass
|
|
172
|
+
r0IR = airmassImpact(r0IR, zenDist)
|
|
165
173
|
|
|
166
174
|
layerAltitude = np.array(layerAltitude)
|
|
167
175
|
layerAltitude *= 1/np.cos(zenDist*np.pi/180) # layers appear further away
|
|
@@ -184,12 +192,9 @@ def exnihilo_scao_psf():
|
|
|
184
192
|
# Here is the position of the object in the field
|
|
185
193
|
offx, offy = (10., 0.)
|
|
186
194
|
|
|
187
|
-
|
|
188
|
-
#################
|
|
189
195
|
# PSF generation
|
|
190
196
|
|
|
191
|
-
# Let's
|
|
192
|
-
# frequencies)
|
|
197
|
+
# Let's define some basic parameters (arrays of spatial frequencies)
|
|
193
198
|
kx, ky, uk = computeSpatialFreqArrays(N, pixelSize, wavelengthIR)
|
|
194
199
|
M4 = defineDmFrequencyArea(kx, ky, rotdegree)
|
|
195
200
|
|
|
@@ -215,22 +220,16 @@ def exnihilo_scao_psf():
|
|
|
215
220
|
FTOtel = computeEeltOTF(pup)
|
|
216
221
|
psf = core_generatePsf(Dphi, FTOtel)
|
|
217
222
|
|
|
218
|
-
print(
|
|
219
|
-
plt.imshow(
|
|
223
|
+
print("Strehl is ", psf.max())
|
|
224
|
+
plt.imshow(np.log(psf))
|
|
220
225
|
|
|
221
226
|
return psf
|
|
222
227
|
|
|
223
228
|
|
|
224
|
-
#
|
|
225
|
-
# | | | |___ ___ / ___|__ _ ___ ___ |___ /
|
|
226
|
-
# | | | / __|/ _ \ | | / _` / __|/ _ \ |_ \
|
|
227
|
-
# | |_| \__ \ __/ | |__| (_| \__ \ __/ ___) |
|
|
228
|
-
# \___/|___/\___| \____\__,_|___/\___| |____/
|
|
229
|
-
|
|
229
|
+
# use-case 3
|
|
230
230
|
|
|
231
231
|
def instantaneous_scao_psf():
|
|
232
232
|
"""
|
|
233
|
-
|
|
234
233
|
The dirty one.
|
|
235
234
|
Let's try to simulate the fluctuations due to short exposures.
|
|
236
235
|
|
|
@@ -275,10 +274,3 @@ def instantaneous_scao_psf():
|
|
|
275
274
|
# Here, possibilities are infinite ..
|
|
276
275
|
# You can add some static aberrations, etc etc,
|
|
277
276
|
# and generate all the PSFs you want.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
from astropy.io import fits
|
|
3
5
|
from astropy.table import Table
|
|
4
6
|
|
|
5
7
|
from matplotlib import pyplot as plt
|
|
6
|
-
from matplotlib.colors import LogNorm
|
|
7
8
|
|
|
8
9
|
from anisocado import AnalyticalScaoPsf
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
def strehl_map(r=25, dr=3, **kwargs):
|
|
12
|
+
def strehl_map(r: int = 25, dr: int = 3, **kwargs):
|
|
12
13
|
psf = AnalyticalScaoPsf(**kwargs)
|
|
13
14
|
x, y = np.mgrid[-r:r+dr:dr, -r:dr+r:dr]
|
|
14
15
|
|
|
15
|
-
strlmap = np.
|
|
16
|
+
strlmap = np.zeros_like(x)
|
|
16
17
|
for i in range(len(x)):
|
|
17
18
|
for j in range(len(y)):
|
|
18
19
|
psf.shift_off_axis(x[i, j], y[i, j])
|
|
@@ -22,11 +23,11 @@ def strehl_map(r=25, dr=3, **kwargs):
|
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def on_axis_strehl_for_kernel_size(Narr=(128, 512, 2048), **kwargs):
|
|
25
|
-
"""Only for the on-axis kernel"""
|
|
26
|
+
"""Only for the on-axis kernel."""
|
|
26
27
|
return [AnalyticalScaoPsf(N=N, **kwargs).strehl_ratio for N in Narr]
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
def make_psf_grid(r=14, dr=7, **kwargs):
|
|
30
|
+
def make_psf_grid(r: int = 14, dr: int = 7, **kwargs):
|
|
30
31
|
psf = AnalyticalScaoPsf(**kwargs)
|
|
31
32
|
x, y = np.mgrid[-r:r+1:dr, -r:r+1:dr]
|
|
32
33
|
|
|
@@ -39,26 +40,30 @@ def make_psf_grid(r=14, dr=7, **kwargs):
|
|
|
39
40
|
return psf_grid
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
def make_image_of_psf_grid(
|
|
43
|
+
def make_image_of_psf_grid(
|
|
44
|
+
filt_name: str = "Ks",
|
|
45
|
+
wave: float = 2.15,
|
|
46
|
+
for_joss: bool = True
|
|
47
|
+
) -> None:
|
|
43
48
|
psf_grid = make_psf_grid(wavelength=wave, N=128)
|
|
44
49
|
|
|
45
50
|
plt.figure(figsize=(10, 10))
|
|
46
51
|
i = 0
|
|
47
52
|
for y in range(5):
|
|
48
53
|
for x in range(5):
|
|
49
|
-
plt.subplot(5, 5, 1+x+5*(4-y))
|
|
50
|
-
plt.imshow(psf_grid[i], origin="
|
|
54
|
+
plt.subplot(5, 5, 1 + x + 5 * (4 - y))
|
|
55
|
+
plt.imshow(psf_grid[i], origin="lower", norm="log")
|
|
51
56
|
plt.axis("off")
|
|
52
|
-
plt.title("({
|
|
57
|
+
plt.title(f"({7*x - 14}, {7*x - 14})")
|
|
53
58
|
i += 1
|
|
54
59
|
|
|
55
60
|
if for_joss:
|
|
56
61
|
plt.tight_layout()
|
|
57
|
-
path = "../docs/joss_paper/{}-band_psf_grid"
|
|
62
|
+
path = f"../docs/joss_paper/{filt_name}-band_psf_grid"
|
|
58
63
|
plt.savefig(path+".png", format="png")
|
|
59
64
|
plt.savefig(path+".pdf", format="pdf")
|
|
60
65
|
else:
|
|
61
|
-
plt.suptitle("{}-band ({}um) SCAO PSFs"
|
|
66
|
+
plt.suptitle(f"{filt_name}-band ({wave} um) SCAO PSFs")
|
|
62
67
|
|
|
63
68
|
|
|
64
69
|
# make_image_of_psf_grid("Ks", 2.15)
|
|
@@ -66,7 +71,12 @@ def make_image_of_psf_grid(filt_name="Ks", wave=2.15, for_joss=True):
|
|
|
66
71
|
# make_image_of_psf_grid("J", 1.2)
|
|
67
72
|
|
|
68
73
|
|
|
69
|
-
def make_simcado_psf_file(
|
|
74
|
+
def make_simcado_psf_file(
|
|
75
|
+
coords,
|
|
76
|
+
wavelengths,
|
|
77
|
+
header_cards=None,
|
|
78
|
+
**kwargs,
|
|
79
|
+
) -> fits.HDUList:
|
|
70
80
|
"""
|
|
71
81
|
Generate a set of Field-Varying PSF cubes for use with SimCADO
|
|
72
82
|
|
|
@@ -142,7 +152,7 @@ def make_simcado_psf_file(coords, wavelengths, header_cards=None, **kwargs):
|
|
|
142
152
|
|
|
143
153
|
psf_hdus = []
|
|
144
154
|
for wave in wavelengths:
|
|
145
|
-
print("Making psf cube for {} um"
|
|
155
|
+
print(f"Making psf cube for {wave} um")
|
|
146
156
|
psf = AnalyticalScaoPsf(wavelength=wave, **kwargs)
|
|
147
157
|
kernel_cube = [psf.shift_off_axis(dx, dy) for dx, dy in coords]
|
|
148
158
|
|
|
@@ -158,9 +168,12 @@ def make_simcado_psf_file(coords, wavelengths, header_cards=None, **kwargs):
|
|
|
158
168
|
return hdulist
|
|
159
169
|
|
|
160
170
|
|
|
161
|
-
def field_positions_for_simcado_psf(
|
|
171
|
+
def field_positions_for_simcado_psf(
|
|
172
|
+
radii: list[float] | None = None,
|
|
173
|
+
theta: float = 45.,
|
|
174
|
+
) -> list[tuple[float, float]]:
|
|
162
175
|
"""
|
|
163
|
-
|
|
176
|
+
Generate a list of field position where the PSF will be sampled.
|
|
164
177
|
|
|
165
178
|
The PSF will be sampled at intervals of ``theta`` around concentric circles
|
|
166
179
|
placed at distances ``radii`` from the centre of the field of view.
|
|
@@ -189,10 +202,9 @@ def field_positions_for_simcado_psf(radii=None, theta=45):
|
|
|
189
202
|
hdu = anisocado.make_simcado_psf_file(cds, wavelengths=[1.2, 1.6, 2.2])
|
|
190
203
|
|
|
191
204
|
"""
|
|
192
|
-
|
|
193
205
|
coords = [(0, 0)]
|
|
194
206
|
if radii is None:
|
|
195
|
-
radii =
|
|
207
|
+
radii = [1, 2, 4, 8, 16, 32]
|
|
196
208
|
for r in radii:
|
|
197
209
|
for ang in np.arange(0, 360, theta):
|
|
198
210
|
coords += [(r * np.cos(np.deg2rad(ang)),
|
|
@@ -205,11 +217,9 @@ def make_strehl_map_from_coords(coords):
|
|
|
205
217
|
x, y = np.array(coords).T
|
|
206
218
|
|
|
207
219
|
from scipy.interpolate import griddata
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return map
|
|
214
|
-
|
|
220
|
+
smap = griddata((x, y), np.arange(len(x)),
|
|
221
|
+
np.array(np.meshgrid(np.arange(-25, 26),
|
|
222
|
+
np.arange(-25, 26))).T,
|
|
223
|
+
method="nearest")
|
|
215
224
|
|
|
225
|
+
return smap
|
|
@@ -1,15 +1,37 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Contains AnalyticalScaoPsf, the main product of this package."""
|
|
3
|
+
|
|
1
4
|
import warnings
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
2
7
|
from matplotlib import pyplot as plt
|
|
3
8
|
from matplotlib.colors import LogNorm
|
|
4
9
|
|
|
5
10
|
from astropy.io import fits
|
|
6
11
|
|
|
7
|
-
from .psf_utils import
|
|
12
|
+
from .psf_utils import (
|
|
13
|
+
get_atmospheric_turbulence,
|
|
14
|
+
computeEeltOTF,
|
|
15
|
+
computeSpatialFreqArrays,
|
|
16
|
+
get_profile_defaults,
|
|
17
|
+
anisoplanaticSpectrum,
|
|
18
|
+
convertSpectrum2Dphi,
|
|
19
|
+
defineDmFrequencyArea,
|
|
20
|
+
computeBpSpectrum,
|
|
21
|
+
otherSpectrum,
|
|
22
|
+
airmassImpact,
|
|
23
|
+
fittingSpectrum,
|
|
24
|
+
aliasingSpectrum,
|
|
25
|
+
computeWiener,
|
|
26
|
+
r0Converter,
|
|
27
|
+
fake_generatePupil,
|
|
28
|
+
core_generatePsf,
|
|
29
|
+
)
|
|
8
30
|
|
|
9
31
|
|
|
10
32
|
class AnalyticalScaoPsf:
|
|
11
33
|
"""
|
|
12
|
-
A class to generate SCAO PSFs for the ELT
|
|
34
|
+
A class to generate SCAO PSFs for the ELT.
|
|
13
35
|
|
|
14
36
|
It is important to note that original PSF is generated for an on-axis
|
|
15
37
|
guide star (``self.psf_on_axis``) at a specific wavelength. This PSF is
|
|
@@ -31,7 +53,8 @@ class AnalyticalScaoPsf:
|
|
|
31
53
|
wavelength : float
|
|
32
54
|
[um] Default: 2.15 um. Wavelength for which the PSF should be generated
|
|
33
55
|
rotdegree : float
|
|
34
|
-
[deg] Default: 0 deg. Rotation angle of the pupil w.r.t the plane of
|
|
56
|
+
[deg] Default: 0 deg. Rotation angle of the pupil w.r.t the plane of
|
|
57
|
+
the optical axis.
|
|
35
58
|
nmRms : float
|
|
36
59
|
[nm] Default: 100 nm. Residual wavefront error of the system
|
|
37
60
|
L0 : float
|
|
@@ -142,13 +165,13 @@ class AnalyticalScaoPsf:
|
|
|
142
165
|
self._kernel_sum = None
|
|
143
166
|
self.seed = None
|
|
144
167
|
self.rng = None
|
|
145
|
-
|
|
168
|
+
|
|
146
169
|
self.__dict__.update(psf_on_axis)
|
|
147
170
|
self.update()
|
|
148
171
|
|
|
149
|
-
def update(self, **kwargs):
|
|
172
|
+
def update(self, **kwargs) -> None:
|
|
150
173
|
"""
|
|
151
|
-
|
|
174
|
+
Update the parameter needed to generate a PSF and/or shift if off-axis.
|
|
152
175
|
|
|
153
176
|
Valid parameter names can be found in ``self.kwarg_names``
|
|
154
177
|
|
|
@@ -156,10 +179,10 @@ class AnalyticalScaoPsf:
|
|
|
156
179
|
self._wave_m = self.wavelength
|
|
157
180
|
if self.wavelength > 0.1: # assume its in um
|
|
158
181
|
self._wave_m *= 1e-6
|
|
159
|
-
|
|
182
|
+
|
|
160
183
|
for key in kwargs:
|
|
161
184
|
if key not in self.kwarg_names:
|
|
162
|
-
warnings.warn("{} not found in self.kwarg_names"
|
|
185
|
+
warnings.warn(f"{key} not found in self.kwarg_names")
|
|
163
186
|
|
|
164
187
|
self.__dict__.update(kwargs)
|
|
165
188
|
if self.seed is not None:
|
|
@@ -208,7 +231,7 @@ class AnalyticalScaoPsf:
|
|
|
208
231
|
self.psf_on_axis = self.make_psf()
|
|
209
232
|
self.psf_latest = self.psf_on_axis
|
|
210
233
|
|
|
211
|
-
def make_psf(self):
|
|
234
|
+
def make_psf(self) -> np.ndarray:
|
|
212
235
|
"""
|
|
213
236
|
Generates a analytical SCAO PSF for a long (>10 sec) exposure
|
|
214
237
|
|
|
@@ -218,7 +241,7 @@ class AnalyticalScaoPsf:
|
|
|
218
241
|
|
|
219
242
|
Returns
|
|
220
243
|
-------
|
|
221
|
-
psf :
|
|
244
|
+
psf : np.ndarray
|
|
222
245
|
The PSF kernel
|
|
223
246
|
|
|
224
247
|
Examples
|
|
@@ -234,7 +257,8 @@ class AnalyticalScaoPsf:
|
|
|
234
257
|
dx, dy, self._wave_m,
|
|
235
258
|
self.kx, self.ky, self.W, self.M4)
|
|
236
259
|
Wfit = fittingSpectrum(self.W, self.M4)
|
|
237
|
-
Walias = aliasingSpectrum(
|
|
260
|
+
Walias = aliasingSpectrum(
|
|
261
|
+
self.kx, self.ky, self.r0IR, self.L0, self.M4)
|
|
238
262
|
Wbp = computeBpSpectrum(self.kx, self.ky, self.V, self.Fe, self.tret,
|
|
239
263
|
self.gain, self.W, self.M4)
|
|
240
264
|
Wother = otherSpectrum(self.nmRms, self.M4, self.uk, self._wave_m)
|
|
@@ -257,27 +281,22 @@ class AnalyticalScaoPsf:
|
|
|
257
281
|
|
|
258
282
|
return psf
|
|
259
283
|
|
|
260
|
-
def shift_off_axis(self, dx, dy):
|
|
284
|
+
def shift_off_axis(self, dx: float, dy: float) -> np.ndarray:
|
|
261
285
|
"""
|
|
262
|
-
|
|
286
|
+
Shift the on-axis PSF off axis by an amount ``(dx, dy)`` in arcsec.
|
|
263
287
|
|
|
264
288
|
Parameters
|
|
265
289
|
----------
|
|
266
290
|
dx, dy : float
|
|
267
291
|
[arcsec] Offset in each of the dimensions relative to the plane of
|
|
268
|
-
the optical axis
|
|
292
|
+
the optical axis.
|
|
269
293
|
|
|
270
294
|
Returns
|
|
271
295
|
-------
|
|
272
|
-
psf :
|
|
273
|
-
The PSF kernel
|
|
274
|
-
|
|
275
|
-
Examples
|
|
276
|
-
--------
|
|
277
|
-
|
|
296
|
+
psf : np.ndarray
|
|
297
|
+
The PSF kernel.
|
|
278
298
|
|
|
279
299
|
"""
|
|
280
|
-
|
|
281
300
|
self.x_last = dx
|
|
282
301
|
self.y_last = dy
|
|
283
302
|
|
|
@@ -302,9 +321,13 @@ class AnalyticalScaoPsf:
|
|
|
302
321
|
|
|
303
322
|
return psf
|
|
304
323
|
|
|
305
|
-
def make_short_exposure_psf(
|
|
324
|
+
def make_short_exposure_psf(
|
|
325
|
+
self,
|
|
326
|
+
dit: float = 1.0,
|
|
327
|
+
screen_step_length: float = 0.5,
|
|
328
|
+
) -> np.ndarray:
|
|
306
329
|
"""
|
|
307
|
-
|
|
330
|
+
Return a PSF for an 'short' exposure time of ``DIT``.
|
|
308
331
|
|
|
309
332
|
The PSF kernel will be a single 2D array made from N stacked
|
|
310
333
|
instantaneous PSFs, where the instantaneous PSFs are generated at time
|
|
@@ -313,23 +336,18 @@ class AnalyticalScaoPsf:
|
|
|
313
336
|
|
|
314
337
|
Parameters
|
|
315
338
|
----------
|
|
316
|
-
dit : float
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
screen_step_length : float
|
|
339
|
+
dit : float, optional
|
|
340
|
+
Exposure time for the PSF in seconds. The default is 1.0 s.
|
|
341
|
+
screen_step_length : float, optional
|
|
320
342
|
[m] Sample step length for atmospheric phase screen
|
|
321
343
|
Default is 0.5m - the length of the M4 actuator pitch
|
|
322
344
|
|
|
323
345
|
Returns
|
|
324
346
|
-------
|
|
325
|
-
psfLE :
|
|
326
|
-
|
|
327
|
-
Examples
|
|
328
|
-
--------
|
|
329
|
-
|
|
347
|
+
psfLE : np.ndarray
|
|
348
|
+
DESCRIPTION.
|
|
330
349
|
|
|
331
350
|
"""
|
|
332
|
-
|
|
333
351
|
# The dirty one.
|
|
334
352
|
# Let's try to simulate the fluctuations due to short exposures.
|
|
335
353
|
Waniso = anisoplanaticSpectrum(self.Cn2h, self.layerAltitude, self.L0,
|
|
@@ -337,7 +355,8 @@ class AnalyticalScaoPsf:
|
|
|
337
355
|
self._wave_m, self.kx, self.ky,
|
|
338
356
|
self.W, self.M4)
|
|
339
357
|
Wfit = fittingSpectrum(self.W, self.M4)
|
|
340
|
-
Walias = aliasingSpectrum(
|
|
358
|
+
Walias = aliasingSpectrum(
|
|
359
|
+
self.kx, self.ky, self.r0IR, self.L0, self.M4)
|
|
341
360
|
Wbp = computeBpSpectrum(self.kx, self.ky, self.V, self.Fe, self.tret,
|
|
342
361
|
self.gain, self.W, self.M4)
|
|
343
362
|
Wother = otherSpectrum(self.nmRms, self.M4, self.uk, self._wave_m)
|
|
@@ -386,26 +405,26 @@ class AnalyticalScaoPsf:
|
|
|
386
405
|
|
|
387
406
|
@property
|
|
388
407
|
def strehl_ratio(self):
|
|
389
|
-
"""Return
|
|
408
|
+
"""Return a Strehl ratio of the kernel in ``self.psf_latest``."""
|
|
390
409
|
return np.max(self.psf_latest) * self._kernel_sum
|
|
391
410
|
|
|
392
411
|
@property
|
|
393
412
|
def kernel(self):
|
|
394
|
-
"""Return the kernel held in ``self.psf_latest
|
|
413
|
+
"""Return the kernel held in ``self.psf_latest``."""
|
|
395
414
|
return self.psf_latest
|
|
396
415
|
|
|
397
416
|
@property
|
|
398
417
|
def hdu(self):
|
|
399
|
-
"""Return the ``ImageHDU`` from ``get_hdu()
|
|
418
|
+
"""Return the ``ImageHDU`` from ``get_hdu()``."""
|
|
400
419
|
return self.get_hdu()
|
|
401
420
|
|
|
402
|
-
def writeto(self, **kwargs):
|
|
403
|
-
"""
|
|
421
|
+
def writeto(self, **kwargs) -> None:
|
|
422
|
+
"""Call the ``writeto`` method of the ImageHDU from ``self.hdu``."""
|
|
404
423
|
self.hdu.writeto(**kwargs)
|
|
405
424
|
|
|
406
|
-
def get_hdu(self, **kwargs):
|
|
425
|
+
def get_hdu(self, **kwargs) -> fits.ImageHDU:
|
|
407
426
|
"""
|
|
408
|
-
|
|
427
|
+
Make an ``ImageHDU`` with the kernel and relevant header info.
|
|
409
428
|
|
|
410
429
|
Additional keyword-value pairs can be passed to the header as kwargs
|
|
411
430
|
|
|
@@ -418,7 +437,7 @@ class AnalyticalScaoPsf:
|
|
|
418
437
|
|
|
419
438
|
hdr = fits.Header()
|
|
420
439
|
hdr["CDELT1"] = self.pixelSize / 3600. # because pixelSize is in arcsec
|
|
421
|
-
hdr["CDELT2"] = self.pixelSize / 3600.
|
|
440
|
+
hdr["CDELT2"] = self.pixelSize / 3600.
|
|
422
441
|
hdr["CRVAL1"] = self.x_last / 3600.
|
|
423
442
|
hdr["CRVAL2"] = self.y_last / 3600.
|
|
424
443
|
hdr["CRPIX1"] = w / 2.
|
|
@@ -439,9 +458,7 @@ class AnalyticalScaoPsf:
|
|
|
439
458
|
|
|
440
459
|
return hdu_psf
|
|
441
460
|
|
|
442
|
-
def plot_psf(self, which="psf_latest"):
|
|
443
|
-
"""
|
|
444
|
-
plt.imshow(getattr(self, which).T, origin='l', norm=
|
|
445
|
-
print(
|
|
446
|
-
|
|
447
|
-
|
|
461
|
+
def plot_psf(self, which="psf_latest") -> None:
|
|
462
|
+
"""Plot a logscale PSF kernel: ["psf_latest", "psf_on_axis"]."""
|
|
463
|
+
plt.imshow(getattr(self, which).T, origin='l', norm="log")
|
|
464
|
+
print(f"Strehl ratio of {which} is {self.psf_latest.max()}")
|