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 CHANGED
@@ -1,2 +1,11 @@
1
1
  from .psf import AnalyticalScaoPsf
2
- from .misc import field_positions_for_simcado_psf, make_simcado_psf_file
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"
anisocado/_anisocado.py CHANGED
@@ -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 'move' it off-axis, let's say (+15, +20) arcsec.
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='l')
54
- print('Strehl ratio of initial psf is ', psf.max())
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.) # in arcsecs
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('Strehl off-axis is', psf_aniso.max())
108
- plt.imshow(psf_aniso.T, origin='l' )
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
- r0IR = r0Converter(r0Vis, 500e-9, wavelengthIR) # convert r0 at 500nm to IR
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
- r0IR = airmassImpact(r0IR, zenDist) # apparent seeing degrades with airmass
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 go. Let's define some basic parameters (arrays of spatial
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('Strehl is ', psf.max())
219
- plt.imshow( np.log(psf) )
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
-
anisocado/misc.py CHANGED
@@ -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.zeros(x.shape)
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(filt_name="Ks", wave=2.15, for_joss=True):
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="l", norm=LogNorm())
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("({}, {})".format((7*x-14), (7*x-14)))
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".format(filt_name)
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".format(filt_name, wave))
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(coords, wavelengths, header_cards=None, **kwargs):
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".format(wave))
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(radii=None, theta=45):
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
- Generates a list of field position where the PSF will be sampled
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 = [1, 2, 4, 8, 16, 32]
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
- map = griddata((x, y), np.arange(len(x)),
209
- np.array(np.meshgrid(np.arange(-25, 26),
210
- np.arange(-25, 26))).T,
211
- method="nearest")
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
anisocado/psf.py CHANGED
@@ -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 the optical axis
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
- Updates the parameter needed to generate a PSF and/or shift if off-axis
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".format(key))
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 : array
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(self.kx, self.ky, self.r0IR, self.L0, self.M4)
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
- Shifts the on-axis PSF off axis by an amount ``(dx, dy)`` in arcsec
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 : array
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(self, dit=1.0, screen_step_length=0.5):
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
- Returns a PSF for an 'short' exposure time of ``DIT``
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
- [s] Default is 1.0 sec. Exposure time for the PSF
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 : array
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(self.kx, self.ky, self.r0IR, self.L0, self.M4)
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 an Strehl ratio of the kernel in ``self.psf_latest``"""
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
- """Calls the ``writeto`` method of the ImageHDU from ``self.hdu``"""
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
- Makes an ``ImageHDU`` with the kernel and relevant header info
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
- """Plots a logscale PSF kernel: ["psf_latest", "psf_on_axis"]"""
444
- plt.imshow(getattr(self, which).T, origin='l', norm=LogNorm())
445
- print('Strehl ratio of {} is {}'.format(which, self.psf_latest.max()))
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()}")