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/photometry.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Photometric budget
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from aopera.readconfig import set_attribute, read_config_file, read_config_tiptop
|
|
7
|
+
from aopera.utils import rad2arcsec
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
CONSTANT = {'h':6.626e-34, 'c':2.998e8, 'kb':1.381e-23}
|
|
11
|
+
|
|
12
|
+
def black_body(wvl, temp):
|
|
13
|
+
"""
|
|
14
|
+
Black body spectrum [W/m2/sr/m]
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
wvl : float, np.array
|
|
19
|
+
Wavelengths [m] at which to compute the spectrum.
|
|
20
|
+
temp : float
|
|
21
|
+
Temperature [K] of the black body.
|
|
22
|
+
"""
|
|
23
|
+
return 2*CONSTANT['h']*CONSTANT['c']**2/((np.exp(CONSTANT['h']*CONSTANT['c']/(wvl*CONSTANT['kb']*temp))-1.0)*wvl**5)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def black_body_sun(wvl):
|
|
27
|
+
"""
|
|
28
|
+
Sun black body spectrum [W/m2/m] as seen from Earth distance.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
wvl : float, np.array
|
|
33
|
+
Wavelengths [m] at which to compute the spectrum.
|
|
34
|
+
"""
|
|
35
|
+
temp = 5780 # surface temperature [Kelvin]
|
|
36
|
+
solid_angle = 6.8*1e-5 # Sun disk seen from Earth [steradian]
|
|
37
|
+
return black_body(wvl, temp) * solid_angle
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def atmospheric_transmission(zenith_rad, t_zenith=0.8):
|
|
41
|
+
"""Atmospheric transmission"""
|
|
42
|
+
return t_zenith ** (1/np.cos(zenith_rad))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def satellite_flux(wvl_min, wvl_max, phase_angle=65, albedo=0.15):
|
|
46
|
+
"""
|
|
47
|
+
Number of photons per second per steradian per m² of satellite surface.
|
|
48
|
+
The received number of photons on a detector will be:
|
|
49
|
+
N = satellite_flux(...) * T_exposure * S_satellite * S_telescope / distance² * throughput
|
|
50
|
+
"""
|
|
51
|
+
incidence = 2/(3*np.pi) * (np.sin(np.deg2rad(phase_angle)) + (np.pi - np.deg2rad(phase_angle)) * np.cos(np.deg2rad(phase_angle)))
|
|
52
|
+
wvl = np.linspace(wvl_min, wvl_max, 2000)
|
|
53
|
+
spectrum = black_body_sun(wvl) * wvl/(CONSTANT['h']*CONSTANT['c']) # [photon/s/wvl]
|
|
54
|
+
nb_ph_sun = np.trapezoid(spectrum, wvl)
|
|
55
|
+
sun_size_idl_correction_factor = 3.1
|
|
56
|
+
return nb_ph_sun * albedo * incidence / sun_size_idl_correction_factor
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Band:
|
|
60
|
+
# These values come from OOMAO [Conan and Correia]
|
|
61
|
+
# [central wvl, bandwidth, zeropoint]
|
|
62
|
+
BANDS = {
|
|
63
|
+
"V": [ 550e-9, 90e-9, 3.3e12],
|
|
64
|
+
"R": [ 640e-9, 150e-9, 4e12],
|
|
65
|
+
"I": [ 790e-9, 150e-9, 2.7e12],
|
|
66
|
+
"J": [1215e-9, 260e-9, 1.9e12],
|
|
67
|
+
"H": [1654e-9, 290e-9, 1.1e12]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def __init__(self, band):
|
|
71
|
+
self.band = band
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def band(self):
|
|
75
|
+
return self._band
|
|
76
|
+
|
|
77
|
+
@band.setter
|
|
78
|
+
def band(self, band):
|
|
79
|
+
if band not in Band.BANDS.keys():
|
|
80
|
+
raise KeyError('The required photometric band [%s] has not been implemented'%band)
|
|
81
|
+
self._band = band
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def wavelength(self):
|
|
85
|
+
"""Central wavelength of the band [m]"""
|
|
86
|
+
return Band.BANDS[self.band][0]
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def bandwidth(self):
|
|
90
|
+
"""Bandwidth [m]"""
|
|
91
|
+
return Band.BANDS[self.band][1]
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def zeropoint(self):
|
|
95
|
+
"""Photon per m2 per second at magnitude 0"""
|
|
96
|
+
return Band.BANDS[self.band][2]/368.0 # This factor comes from OOMAO [Conan and Correia]
|
|
97
|
+
|
|
98
|
+
def photon2mag(self, nphot):
|
|
99
|
+
"""Convert number of photons per m2 per second in magnitude"""
|
|
100
|
+
return -2.5*np.log10(nphot/self.zeropoint)
|
|
101
|
+
|
|
102
|
+
def mag2photon(self, mag):
|
|
103
|
+
"""Convert magnitude to number of photons per m2 per second"""
|
|
104
|
+
return self.zeropoint * 10**(-mag/2.5)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class SourceScience:
|
|
108
|
+
def __init__(self, dictionary):
|
|
109
|
+
set_attribute(self, dictionary, 'source_science')
|
|
110
|
+
|
|
111
|
+
def __repr__(self):
|
|
112
|
+
s = 'aopera.SOURCE_SCIENCE\n'
|
|
113
|
+
s += '----------------------\n'
|
|
114
|
+
s += 'wvl : %u nm\n'%self.wvl_nm
|
|
115
|
+
s += 'zenith : %u deg\n'%self.zenith_deg
|
|
116
|
+
return s
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def from_file(filepath, category='source_science'):
|
|
120
|
+
return SourceScience(read_config_file(filepath, category))
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def from_file_tiptop(filepath):
|
|
124
|
+
return SourceScience(read_config_tiptop(filepath)[4])
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def from_oopao(src):
|
|
128
|
+
return SourceScience({'wvl_nm':src.wavelength*1e9,'zenith_deg':0})
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def wvl(self):
|
|
132
|
+
return self.wvl_nm * 1e-9
|
|
133
|
+
|
|
134
|
+
@wvl.setter
|
|
135
|
+
def wvl(self, value):
|
|
136
|
+
self.wvl_nm = value * 1e9
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def zenith_rad(self):
|
|
140
|
+
return self.zenith_deg * np.pi/180
|
|
141
|
+
|
|
142
|
+
@zenith_rad.setter
|
|
143
|
+
def zenith_rad(self, value):
|
|
144
|
+
self.zenith_deg = value * 180/np.pi
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def elevation_rad(self):
|
|
148
|
+
return np.pi/2 - self.zenith_rad
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def elevation_deg(self):
|
|
152
|
+
return 90 - self.zenith_deg
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class SourceWFS:
|
|
157
|
+
def __init__(self, dictionary):
|
|
158
|
+
self.square = False
|
|
159
|
+
set_attribute(self, dictionary, 'source_wfs')
|
|
160
|
+
|
|
161
|
+
def __repr__(self):
|
|
162
|
+
s = 'aopera.SOURCE_WFS\n'
|
|
163
|
+
s += '------------------\n'
|
|
164
|
+
s += 'wvl : %u nm\n'%self.wvl_nm
|
|
165
|
+
s += 'flux : %.2g ph/m²/s\n'%self.flux
|
|
166
|
+
s += 'separation: %.3f arcsec\n'%self.separation
|
|
167
|
+
s += 'size : %.2f arcsec\n'%self.size
|
|
168
|
+
return s
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def from_file(filepath, category='source_wfs'):
|
|
172
|
+
return SourceWFS(read_config_file(filepath, category))
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def from_file_tiptop(filepath):
|
|
176
|
+
return SourceWFS(read_config_tiptop(filepath)[5])
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def from_oopao(src_wfs, src_sci):
|
|
180
|
+
r_wfs, a_wfs = src_wfs.coordinates
|
|
181
|
+
x_wfs = r_wfs * np.cos(a_wfs*np.pi/180)
|
|
182
|
+
y_wfs = r_wfs * np.sin(a_wfs*np.pi/180)
|
|
183
|
+
r_sci, a_sci = src_sci.coordinates
|
|
184
|
+
x_sci = r_sci * np.cos(a_sci*np.pi/180)
|
|
185
|
+
y_sci = r_sci * np.sin(a_sci*np.pi/180)
|
|
186
|
+
logging.warning('Conversion from OOPAO to aopera assumes a point-like WFS source.')
|
|
187
|
+
return SourceWFS({'wvl_nm':src_wfs.wavelength*1e9,
|
|
188
|
+
'flux':src_wfs.nPhoton,
|
|
189
|
+
'separation':np.sqrt((x_sci-x_wfs)**2+(y_sci-y_wfs)**2),
|
|
190
|
+
'angle':np.arctan2(y_wfs-y_sci, x_wfs-x_sci)*180/np.pi,
|
|
191
|
+
'size':0})
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def wvl(self):
|
|
195
|
+
return self.wvl_nm * 1e-9
|
|
196
|
+
|
|
197
|
+
@wvl.setter
|
|
198
|
+
def wvl(self, value):
|
|
199
|
+
self.wvl_nm = value * 1e9
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def separation_x(self):
|
|
203
|
+
return self.separation * np.cos(self.angle*np.pi/180)
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def separation_y(self):
|
|
207
|
+
return self.separation * np.sin(self.angle*np.pi/180)
|
|
208
|
+
|
|
209
|
+
def image(self, nx, tel_diameter, samp):
|
|
210
|
+
xx,yy = np.mgrid[0:nx,0:nx] - nx//2
|
|
211
|
+
pix_size_arcsec = rad2arcsec((self.wvl/tel_diameter) / samp)
|
|
212
|
+
Robj_pix = (self.size/2) / pix_size_arcsec
|
|
213
|
+
if self.square:
|
|
214
|
+
obj = (np.abs(xx)<=Robj_pix)*(np.abs(yy)<=Robj_pix)
|
|
215
|
+
else:
|
|
216
|
+
rr = np.sqrt(xx**2+yy**2)
|
|
217
|
+
obj = (rr <= Robj_pix)
|
|
218
|
+
return obj / np.sum(obj)
|
|
219
|
+
|
aopera/readconfig.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Load data from a .INI configuration file
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from configparser import ConfigParser
|
|
7
|
+
import logging
|
|
8
|
+
import numpy as np
|
|
9
|
+
# from aopera.turbulence import WVL_REF_SEEING
|
|
10
|
+
from aopera.utils import arcsec2rad
|
|
11
|
+
|
|
12
|
+
WVL_REF_SEEING = 500e-9 # TODO: solve circular import issue
|
|
13
|
+
|
|
14
|
+
def np_array(x):
|
|
15
|
+
try:
|
|
16
|
+
return np.array(eval(x))
|
|
17
|
+
except:
|
|
18
|
+
return np.array(x)
|
|
19
|
+
|
|
20
|
+
def make_bool(x):
|
|
21
|
+
if type(x) is bool:
|
|
22
|
+
return x
|
|
23
|
+
else:
|
|
24
|
+
return x.lower()=='true'
|
|
25
|
+
|
|
26
|
+
def float_or_none(x):
|
|
27
|
+
if (x is None) or (x == 'None'):
|
|
28
|
+
return None
|
|
29
|
+
else:
|
|
30
|
+
return float(x)
|
|
31
|
+
|
|
32
|
+
# INFO have following structure:
|
|
33
|
+
# key : (string to type converter, mandatory boolean, default value if optional)
|
|
34
|
+
|
|
35
|
+
INFO_ATMO_ABSTRACT = {'lext':(float,True,None), # [m]
|
|
36
|
+
'altitude':(np_array,True,None), # [m]
|
|
37
|
+
'wind_speed':(np_array,True,None), # [m/s]
|
|
38
|
+
'wind_direction':(np_array,True,None)} # [deg]
|
|
39
|
+
|
|
40
|
+
INFO_ATMO_SEEING = dict(INFO_ATMO_ABSTRACT)
|
|
41
|
+
INFO_ATMO_SEEING.update({'seeing':(float,True,None), # [arcsec]
|
|
42
|
+
'cn2dh_ratio':(np_array,True,None)}) # [no unit]
|
|
43
|
+
|
|
44
|
+
INFO_ATMO_CN2DH = dict(INFO_ATMO_ABSTRACT)
|
|
45
|
+
INFO_ATMO_CN2DH.update({'cn2dh':(np_array,True,None)}) # [m^(-1/3)]
|
|
46
|
+
|
|
47
|
+
INFO_SOURCE_SCIENCE = {'wvl_nm':(float,True,None), # [nm]
|
|
48
|
+
'zenith_deg':(float,True,None)} # [deg]
|
|
49
|
+
|
|
50
|
+
INFO_SOURCE_WFS = {'wvl_nm':(float,True,None), # [nm]
|
|
51
|
+
'flux':(float,True,None), # [ph/m²/s]
|
|
52
|
+
'separation':(float,True,None), # [arcsec]
|
|
53
|
+
'angle':(float,True,None), # [deg]
|
|
54
|
+
'size':(float,True,None)} # [arcsec]
|
|
55
|
+
|
|
56
|
+
INFO_PUPIL = {'diameter':(float,True,None), # [m]
|
|
57
|
+
'occultation':(float,True,None), # [no unit]
|
|
58
|
+
'nact':(int,True,None), # [no unit]
|
|
59
|
+
'ncpa':(float,False,0), # [nm RMS]
|
|
60
|
+
'nmode_ratio':(float,False,1.0)}
|
|
61
|
+
|
|
62
|
+
INFO_RTC = {'freq':(float,True,None), # [Hz]
|
|
63
|
+
'delay':(float,True,None), # [ms]
|
|
64
|
+
'ki':(float,True,None)} # [no unit]
|
|
65
|
+
|
|
66
|
+
INFO_WFS_ABSTRACT = {'lenslet':(int,True,None), # [no unit]
|
|
67
|
+
'ron':(float,True,None), # [e-/pixel]
|
|
68
|
+
'emccd':(make_bool,True,None)} # [no unit]
|
|
69
|
+
|
|
70
|
+
INFO_PWFS = dict(INFO_WFS_ABSTRACT)
|
|
71
|
+
INFO_PWFS.update({'modulation':(float,True,None),
|
|
72
|
+
'og_compensation':(bool,False,False)}) # [lambda/D]
|
|
73
|
+
|
|
74
|
+
INFO_SHWFS = dict(INFO_WFS_ABSTRACT)
|
|
75
|
+
INFO_SHWFS.update({'samp':(float,True,None), # [no unit]
|
|
76
|
+
'npix_cog':(int,True,None),
|
|
77
|
+
'weight':(float_or_none,False,None)}) # [no unit]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
INFO = {'atmosphere_seeing':INFO_ATMO_SEEING,
|
|
81
|
+
'atmosphere_cn2dh':INFO_ATMO_CN2DH,
|
|
82
|
+
'source_science':INFO_SOURCE_SCIENCE,
|
|
83
|
+
'source_wfs':INFO_SOURCE_WFS,
|
|
84
|
+
'pupil':INFO_PUPIL,
|
|
85
|
+
'rtc':INFO_RTC,
|
|
86
|
+
'pwfs':INFO_PWFS,
|
|
87
|
+
'shwfs':INFO_SHWFS}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def read_config_file(filepath, category):
|
|
91
|
+
"""Generic function to read a specific category of a INI file"""
|
|
92
|
+
cfg = ConfigParser()
|
|
93
|
+
cfg.optionxform = str
|
|
94
|
+
out = cfg.read(filepath)
|
|
95
|
+
if len(out)==0:
|
|
96
|
+
# Try in the 'data' folder
|
|
97
|
+
filepath_data = os.path.dirname(os.path.abspath(__file__)) + os.path.sep + 'data' + os.path.sep + filepath
|
|
98
|
+
out = cfg.read(filepath_data)
|
|
99
|
+
if len(out)==0:
|
|
100
|
+
raise FileNotFoundError("The configuration file has not been found")
|
|
101
|
+
else:
|
|
102
|
+
logging.info('File <%s> has been found in <aopera/data/>'%filepath)
|
|
103
|
+
if not category in cfg.keys():
|
|
104
|
+
raise ValueError("The category [%s] does not appear in the configuration file"%category)
|
|
105
|
+
return cfg[category]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def read_config_tiptop(filepath):
|
|
109
|
+
"""
|
|
110
|
+
Read a TIPTOP INI file to generate aopera compatible dictionaries
|
|
111
|
+
"""
|
|
112
|
+
cfg = ConfigParser()
|
|
113
|
+
cfg.optionxform = str
|
|
114
|
+
out = cfg.read(filepath)
|
|
115
|
+
if len(out)==0:
|
|
116
|
+
raise FileNotFoundError("The configuration file has not been found")
|
|
117
|
+
|
|
118
|
+
cfg_tel = cfg['telescope']
|
|
119
|
+
cfg_atm = cfg['atmosphere']
|
|
120
|
+
dm_act = eval(cfg['DM']['NumberActuators'])
|
|
121
|
+
|
|
122
|
+
if len(dm_act)>1: #TODO: SCAO only
|
|
123
|
+
raise ValueError('TIPTOP file has more than one DM, this is not compatible with aopera')
|
|
124
|
+
|
|
125
|
+
if float(cfg_atm['Wavelength']) != WVL_REF_SEEING:
|
|
126
|
+
raise ValueError('TIPTOP file must define seeing at %u nm'%(WVL_REF_SEEING*1e9))
|
|
127
|
+
|
|
128
|
+
if 'SensorFrameRate_LO' in cfg['RTC'].keys():
|
|
129
|
+
raise ValueError('Cannot use a Low Order split with aopera')
|
|
130
|
+
|
|
131
|
+
area_shape = cfg['DM']['AoArea'].replace('\'','')
|
|
132
|
+
if area_shape == 'circle':
|
|
133
|
+
nmode_ratio = np.pi/4
|
|
134
|
+
elif area_shape == 'square':
|
|
135
|
+
nmode_ratio = 1
|
|
136
|
+
else:
|
|
137
|
+
raise ValueError('Error when reading Tiptop file, the DM AoArea must be `circle` or `square`')
|
|
138
|
+
|
|
139
|
+
pupil = {'diameter':float(cfg_tel['TelescopeDiameter']),
|
|
140
|
+
'occultation':float(cfg_tel['ObscurationRatio']),
|
|
141
|
+
'nact':dm_act[0],
|
|
142
|
+
'nmode_ratio':nmode_ratio}
|
|
143
|
+
|
|
144
|
+
atmosphere = {'seeing':float(cfg_atm['Seeing']),
|
|
145
|
+
'cn2dh_ratio':np_array(cfg_atm['Cn2Weights']),
|
|
146
|
+
'lext':float(cfg_atm['L0']),
|
|
147
|
+
'altitude':np_array(cfg_atm['Cn2Heights']),
|
|
148
|
+
'wind_speed':np_array(cfg_atm['WindSpeed']),
|
|
149
|
+
'wind_direction':90 - np_array(cfg_atm['WindDirection'])}
|
|
150
|
+
|
|
151
|
+
nb_frame_delay = float(cfg['RTC']['LoopDelaySteps_HO']) - 1 # TIPTOP counts WFS integration as frame delay
|
|
152
|
+
freq = float(cfg['RTC']['SensorFrameRate_HO'])
|
|
153
|
+
|
|
154
|
+
rtc = {'freq':freq,
|
|
155
|
+
'delay':nb_frame_delay/freq*1e3,
|
|
156
|
+
'ki':float(cfg['RTC']['LoopGain_HO'])}
|
|
157
|
+
|
|
158
|
+
if eval(cfg['sources_science']['Zenith'])[0] != 0:
|
|
159
|
+
raise ValueError('aopera evaluates performance at center of array, source science zenith angle should be null.')
|
|
160
|
+
|
|
161
|
+
src_sci_wvl = eval(cfg['sources_science']['Wavelength'])
|
|
162
|
+
if len(src_sci_wvl) > 1:
|
|
163
|
+
raise NotImplementedError('No compatibility between TIPTOP and aopera for multiple science wavelengths.')
|
|
164
|
+
src_sci = {'wvl_nm':src_sci_wvl[0]*1e9,
|
|
165
|
+
'zenith_deg':float(cfg_tel['ZenithAngle'])}
|
|
166
|
+
|
|
167
|
+
cfg_src_wfs = cfg['sources_HO']
|
|
168
|
+
cfg_wfs = cfg['sensor_HO']
|
|
169
|
+
|
|
170
|
+
wfs_type = cfg_wfs['WfsType']
|
|
171
|
+
if int(cfg_wfs['ExcessNoiseFactor']) == 1:
|
|
172
|
+
emccd = False
|
|
173
|
+
elif int(cfg_wfs['ExcessNoiseFactor']) == 2:
|
|
174
|
+
emccd = True
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError('Can only process ExcessNoiseFactor with value 1 or 2')
|
|
177
|
+
|
|
178
|
+
wfs_nb_lenslet = eval(cfg_wfs['NumberLenslets'])
|
|
179
|
+
if len(wfs_nb_lenslet) > 1:
|
|
180
|
+
raise ValueError('TIPTOP file has more than one WFS, this is not compatible with aopera')
|
|
181
|
+
|
|
182
|
+
wfs = {'lenslet':wfs_nb_lenslet[0],
|
|
183
|
+
'ron':float(cfg_wfs['SigmaRON']),
|
|
184
|
+
'emccd':emccd}
|
|
185
|
+
|
|
186
|
+
nb_phot_wfs = eval(cfg_wfs['NumberPhotons'])[0] # nb photon/subap/frame
|
|
187
|
+
wfs_flux = nb_phot_wfs * rtc['freq'] * wfs['lenslet']**2 / pupil['diameter']**2
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
src_wfs = {'wvl_nm':float(cfg_src_wfs['Wavelength'])*1e9,
|
|
191
|
+
'flux':wfs_flux,
|
|
192
|
+
'separation':eval(cfg_src_wfs['Zenith'])[0],
|
|
193
|
+
'angle':eval(cfg_src_wfs['Azimuth'])[0],
|
|
194
|
+
'size':0}
|
|
195
|
+
|
|
196
|
+
if 'Pyramid' in wfs_type:
|
|
197
|
+
wfs['modulation'] = float(cfg_wfs['Modulation'])
|
|
198
|
+
elif 'Shack-Hartmann' in wfs_type:
|
|
199
|
+
logging.warning('TIPTOP and aopera definitions for SH spot CoG algorithm have to be checked.')
|
|
200
|
+
pix_rad = arcsec2rad(float(cfg_wfs['PixelScale'])*1e-3)
|
|
201
|
+
lmbd_dpup = src_wfs['wvl_nm'] * 1e-9 / (pupil['diameter']/wfs['lenslet'])
|
|
202
|
+
wfs['samp'] = lmbd_dpup / pix_rad
|
|
203
|
+
wfs['npix_cog'] = 10
|
|
204
|
+
else:
|
|
205
|
+
raise ValueError('Unknown TIPTOP WFS type')
|
|
206
|
+
|
|
207
|
+
return pupil, atmosphere, wfs, rtc, src_sci, src_wfs
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def read_config_tiptop_samp(filepath):
|
|
211
|
+
"""Read a TIPTOP INI file and compute PSF sampling at WFS wavelength"""
|
|
212
|
+
cfg = ConfigParser()
|
|
213
|
+
cfg.optionxform = str
|
|
214
|
+
out = cfg.read(filepath)
|
|
215
|
+
if len(out)==0:
|
|
216
|
+
raise FileNotFoundError("The configuration file has not been found")
|
|
217
|
+
diameter = float(cfg['telescope']['TelescopeDiameter'])
|
|
218
|
+
wvl_wfs = float(cfg['sources_HO']['Wavelength'])
|
|
219
|
+
pix_mas = float(cfg['sensor_science']['PixelScale'])
|
|
220
|
+
return (wvl_wfs/diameter) / arcsec2rad(pix_mas*1e-3)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def read_config_tiptop_nx(filepath):
|
|
224
|
+
"""Read a TIPTOP INI file and compute PSF sampling at WFS wavelength"""
|
|
225
|
+
cfg = ConfigParser()
|
|
226
|
+
cfg.optionxform = str
|
|
227
|
+
out = cfg.read(filepath)
|
|
228
|
+
if len(out)==0:
|
|
229
|
+
raise FileNotFoundError("The configuration file has not been found")
|
|
230
|
+
return int(cfg['sensor_science']['FieldOfView'])
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def check_dictionary(dictionary, category_name):
|
|
234
|
+
"""
|
|
235
|
+
Check keywords.
|
|
236
|
+
Return a dictionary with optional keywords filled with default value.
|
|
237
|
+
"""
|
|
238
|
+
for k in INFO[category_name].keys():
|
|
239
|
+
if not k in dictionary.keys():
|
|
240
|
+
if INFO[category_name][k][1]: # Keyword is mandatory
|
|
241
|
+
logging.error('The keyword \'%s\' is mandatory in the category \'%s\''%(k,category_name))
|
|
242
|
+
else:
|
|
243
|
+
# logging.warning('The keyword \'%s\' has not been defined in the category \'%s\', it is set to default value \'%s\''%(k,category_name,INFO[category_name][k][2]))
|
|
244
|
+
logging.warning('Undefined keyword set to default value \'%s.%s=%s\''%(category_name,k,INFO[category_name][k][2]))
|
|
245
|
+
try:
|
|
246
|
+
dictionary[k] = INFO[category_name][k][2]
|
|
247
|
+
except: # config parser requires strings
|
|
248
|
+
dictionary[k] = str(INFO[category_name][k][2])
|
|
249
|
+
else:
|
|
250
|
+
try:
|
|
251
|
+
INFO[category_name][k][0](dictionary[k]) # try type conversion
|
|
252
|
+
except:
|
|
253
|
+
logging.error('The keyword \'%s\' in the category \'%s\' does not have the correct type'%(k,category_name))
|
|
254
|
+
for k in dictionary.keys():
|
|
255
|
+
if not k in INFO[category_name].keys():
|
|
256
|
+
logging.warning('The keyword \'%s\' is ignored by the category \'%s\''%(k,category_name))
|
|
257
|
+
return dictionary
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def set_attribute(slf, dictionary, category_name):
|
|
261
|
+
"""
|
|
262
|
+
Set a disctionary as attribute of the Python object `slf`
|
|
263
|
+
"""
|
|
264
|
+
dictionary = check_dictionary(dictionary, category_name)
|
|
265
|
+
for k in INFO[category_name].keys():
|
|
266
|
+
setattr(slf, k, INFO[category_name][k][0](dictionary[k]))
|
|
267
|
+
|