pyExtinction 2.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.
- pyextinction/__init__.py +12 -0
- pyextinction/__main__.py +206 -0
- pyextinction/__main__.py.bak +141 -0
- pyextinction/atmospheric_extinction.py +438 -0
- pyextinction/data/ozoneTemplate.fits +0 -0
- pyextinction-2.0.dist-info/METADATA +128 -0
- pyextinction-2.0.dist-info/RECORD +10 -0
- pyextinction-2.0.dist-info/WHEEL +4 -0
- pyextinction-2.0.dist-info/entry_points.txt +2 -0
- pyextinction-2.0.dist-info/licenses/LICENSE.txt +517 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2013-present Yannick Copin <y.copin@ip2i.in2p3.fr>
|
|
2
|
+
# SPDX-License-Identifier: CECILL-C
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
.. _module:
|
|
6
|
+
|
|
7
|
+
atmopheric_extinction (module)
|
|
8
|
+
==============================
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from importlib.resources import files # Python 3.10+
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from scipy.interpolate import UnivariateSpline
|
|
15
|
+
import matplotlib.pyplot as plt
|
|
16
|
+
from astropy.io import fits
|
|
17
|
+
|
|
18
|
+
# Data path
|
|
19
|
+
PYEXT_PATH = files("pyextinction.data") #: Path to pyExtinction data.
|
|
20
|
+
O3Template = PYEXT_PATH.joinpath('ozoneTemplate.fits') #: Default ozone template
|
|
21
|
+
|
|
22
|
+
EXT2OPT = .92103403719761834 # LOG10/2.5 = Extinction to opt. thickness
|
|
23
|
+
|
|
24
|
+
# Classes ======================================================================
|
|
25
|
+
|
|
26
|
+
class ExtinctionModel:
|
|
27
|
+
"""
|
|
28
|
+
ExtinctionModel class, to build an extinction curve from individual components.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, lbda=None, ozoneTemplate=O3Template, lrefAero=1e4):
|
|
32
|
+
"""
|
|
33
|
+
Extinction model, from:
|
|
34
|
+
|
|
35
|
+
:param lbda: wavelength vector [Å] (default to extended optical range)
|
|
36
|
+
:param ozoneTemplate: name of the ozone template table (see
|
|
37
|
+
:func:`readOzoneTemplate`). By default, use the provided
|
|
38
|
+
ozone template.
|
|
39
|
+
:param lrefAero: aerosol reference wavelength [Å]
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
if lbda is None:
|
|
43
|
+
self.lbda = np.arange(3200, 10001, 10, dtype=float)
|
|
44
|
+
else:
|
|
45
|
+
self.lbda = np.asanyarray(lbda) # Wavelength [Å]
|
|
46
|
+
|
|
47
|
+
# Rayleigh extinction template [mag/airmass] for a pressure of 1 mbar
|
|
48
|
+
self.rayleigh = self.rayleigh_HT74(self.lbda, 1.)
|
|
49
|
+
|
|
50
|
+
# Ozone extinction [mag/airmass]
|
|
51
|
+
self.ozoneName = ozoneTemplate
|
|
52
|
+
|
|
53
|
+
# Read transmission ozone template, interpolate at input
|
|
54
|
+
# wavelengthes, and convert to extinction [mag/airmass] for an
|
|
55
|
+
# ozone column density of 1 DU
|
|
56
|
+
self.ozone, self.ozoneNorm = readOzoneTemplate(self.ozoneName, self.lbda)
|
|
57
|
+
self.ozone /= self.ozoneNorm
|
|
58
|
+
|
|
59
|
+
# Aerosols
|
|
60
|
+
self.lrefAero = lrefAero # Aerosol reference wavelength [Å]
|
|
61
|
+
self.lbdaN = self.lbda / self.lrefAero
|
|
62
|
+
|
|
63
|
+
# Parameters (to be set)
|
|
64
|
+
self.p = self.dp = None
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
|
|
68
|
+
lmin, lmax = self.lbda[[0, -1]]
|
|
69
|
+
dl = self.lbda[1] - self.lbda[0]
|
|
70
|
+
n = len(self.lbda)
|
|
71
|
+
|
|
72
|
+
s = f"""\
|
|
73
|
+
Wavelength domain: {lmin:.0f}-{lmax:.0f} Å by step of {dl:.0f} Å ({n} px)
|
|
74
|
+
Ozone template: {self.ozoneName} ({self.ozoneNorm} DU)
|
|
75
|
+
Aerosol reference wavelength: {self.lrefAero:.0f} Å
|
|
76
|
+
"""
|
|
77
|
+
if self.p is not None:
|
|
78
|
+
p, o3, tau, ang = self.p
|
|
79
|
+
dp, do3, dtau, dang = self.dp
|
|
80
|
+
s += f"""\
|
|
81
|
+
Input extinction parameters:
|
|
82
|
+
Pressure: {p:.0f} ± {dp:.0f} mbar
|
|
83
|
+
Ozone: {o3:.0f} ± {do3:.0f} DU
|
|
84
|
+
Aerosols: optical depth @ refLbda: {tau:.2g} ± {dtau:.2g}
|
|
85
|
+
angstrom exponent: {ang:.2f} ± {dang:.2f}
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
else:
|
|
89
|
+
s += """\
|
|
90
|
+
Input extinction parameters: not set yet
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
return s
|
|
94
|
+
|
|
95
|
+
def setParams(self, pars, dpars=None):
|
|
96
|
+
"""
|
|
97
|
+
Set physical extinction parameters: pressure, ozone column
|
|
98
|
+
density [Dobson units], aerosol optical depth at reference
|
|
99
|
+
wavelength and aerosol angstrom exponent.
|
|
100
|
+
|
|
101
|
+
:param pars: extinction parameters (*pr*, *oi*, *ai*, *ap*)
|
|
102
|
+
where:
|
|
103
|
+
|
|
104
|
+
- *pr*: surface pressure [mbar]
|
|
105
|
+
- *oi*: ozone intensity [Dobson units]
|
|
106
|
+
- *ai*: aerosol optical depth at reference wavelength
|
|
107
|
+
- *ap*: aerosol angstrom exponent
|
|
108
|
+
|
|
109
|
+
:param dpars: associated standard errors
|
|
110
|
+
|
|
111
|
+
The total atmospheric extinction will then be the sum of
|
|
112
|
+
three components:
|
|
113
|
+
|
|
114
|
+
- Rayleigh extinction: `pr[mbar] * HT74(1 mbar)`
|
|
115
|
+
- Ozone extinction: `oi[DU] * OzoneTemplate(1 DU)`
|
|
116
|
+
- Aerosol extinction: `ai/EXT2OPT * (lbda/lRef)**(-ap)`
|
|
117
|
+
|
|
118
|
+
.. Note:: if `ndim(dpars)==2`, `dpars` is considered as the
|
|
119
|
+
*covariance* matrix of input extinction
|
|
120
|
+
parameters. Therefore, when `ndim(dpars)==1`,
|
|
121
|
+
`self.extinctionErrors(pars, dpars)` is equivalent to
|
|
122
|
+
`self.extinctionErrors(pars, np.diag(dpars)**2)` (note the
|
|
123
|
+
square power).
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
self.p = np.asanyarray(pars)
|
|
127
|
+
if dpars is None:
|
|
128
|
+
self.dp = np.zeros(4)
|
|
129
|
+
else:
|
|
130
|
+
self.dp = np.asanyarray(dpars)
|
|
131
|
+
|
|
132
|
+
def setDefaultParams(self, location='Mauna Kea'):
|
|
133
|
+
"""
|
|
134
|
+
Set default physical extinction parameters from predefined location.
|
|
135
|
+
|
|
136
|
+
:param location: predefined location.
|
|
137
|
+
|
|
138
|
+
================================= =================
|
|
139
|
+
Parameter Value ± Error
|
|
140
|
+
================================= =================
|
|
141
|
+
*Mauna Kea*
|
|
142
|
+
----------------------------------------------------
|
|
143
|
+
Pressure 616 ± 2 mbar
|
|
144
|
+
Ozone column 257 ± 23 DU
|
|
145
|
+
Aerosols optical depth @ 1 micron 0.0076 ± 0.0014
|
|
146
|
+
Aerosols angstrom exponent 1.26 ± 1.33
|
|
147
|
+
================================= =================
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
if location == "Mauna Kea":
|
|
151
|
+
o3, do3 = 257., 23. # Ozone column density [DU]
|
|
152
|
+
ang, dang = 1.26, 1.33 # Ångström exponent
|
|
153
|
+
tau, dtau = 7.6e-3, 1.4e-3 # Aerosol optical depth at 1 micron
|
|
154
|
+
p, dp = 616., 2. # Surface pressure [mbar]
|
|
155
|
+
else:
|
|
156
|
+
raise ValueError(f"Unknown location {location!r}.")
|
|
157
|
+
|
|
158
|
+
self.setParams([p, o3, tau, ang],
|
|
159
|
+
dpars=[dp, do3, dtau, dang])
|
|
160
|
+
|
|
161
|
+
def extinctionComponents(self):
|
|
162
|
+
"""
|
|
163
|
+
Compute extinction individual components from extinction
|
|
164
|
+
parameters (see :meth:`setParams`)
|
|
165
|
+
|
|
166
|
+
:return: extinction components 2D-array [rayleigh,ozone,aerosols]
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
return np.array([
|
|
170
|
+
self.p[0] * self.rayleigh, # Rayleigh component
|
|
171
|
+
self.p[1] * self.ozone, # Ozone component
|
|
172
|
+
self.p[2] / EXT2OPT * self.lbdaN**(-self.p[3]), # Aerosols component
|
|
173
|
+
])
|
|
174
|
+
|
|
175
|
+
def extinctionErrors(self):
|
|
176
|
+
"""
|
|
177
|
+
Compute total extinction (diagonal) standard error from extinction
|
|
178
|
+
parameters and associated standard errors (see :meth:`setParams`)
|
|
179
|
+
|
|
180
|
+
:return: total extinction standard error
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
jac = self.jac()
|
|
184
|
+
if np.ndim(self.dp) == 1: # dp is a vector of std (independant) errors
|
|
185
|
+
vExt = np.dot(self.dp**2, jac**2)
|
|
186
|
+
elif np.ndim(self.dp) == 2: # dp is actually a covariance matrix
|
|
187
|
+
vExt = np.dot(np.dot(jac.T, self.dp), jac).diagonal()
|
|
188
|
+
else:
|
|
189
|
+
raise ValueError("Invalid extinction errors.")
|
|
190
|
+
|
|
191
|
+
return np.sqrt(vExt)
|
|
192
|
+
|
|
193
|
+
def extinction(self, pars=None, dpars=None, components=False):
|
|
194
|
+
"""
|
|
195
|
+
Compute total extinction (and associated standard error)
|
|
196
|
+
from extinction parameters (and associated standard errors).
|
|
197
|
+
|
|
198
|
+
:param pars: extinction parameters (see :meth:`setParams`)
|
|
199
|
+
:param dpars: extinction parameter errors (see :meth:`setParams`)
|
|
200
|
+
:param components: return individual extinction components if True
|
|
201
|
+
:return: 2D-array [ext,dext,[components]]
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
if None not in (pars, dpars):
|
|
205
|
+
self.setParams(pars, dpars)
|
|
206
|
+
|
|
207
|
+
comp = self.extinctionComponents() # (ncomp,nlbda)
|
|
208
|
+
ext = comp.sum(axis=0) # (nlbda,)
|
|
209
|
+
dext = self.extinctionErrors() # (nlbda,)
|
|
210
|
+
|
|
211
|
+
if not components: # Return [lbda, ext, dext]
|
|
212
|
+
return np.vstack((ext, dext))
|
|
213
|
+
else: # Return individual components as well
|
|
214
|
+
return np.vstack((ext, dext, comp))
|
|
215
|
+
|
|
216
|
+
def jac(self):
|
|
217
|
+
"""Jacobian of total extinction with respect to extinction
|
|
218
|
+
parameters.
|
|
219
|
+
|
|
220
|
+
:return: jacobian 2D-array (nparam=4,nlbda)
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
jac = np.empty((len(self.p), len(self.lbda)), 'd')
|
|
224
|
+
jac[0] = self.rayleigh # dext/dP
|
|
225
|
+
jac[1] = self.ozone # dext/do3
|
|
226
|
+
jac[2] = self.lbdaN**(-self.p[3]) / EXT2OPT # dext/dtau
|
|
227
|
+
jac[3] = -self.p[2] * jac[2] * np.log(self.lbdaN) # dext/dang
|
|
228
|
+
|
|
229
|
+
return jac
|
|
230
|
+
|
|
231
|
+
@staticmethod
|
|
232
|
+
def rayleigh_HT74(lbda, pressure):
|
|
233
|
+
"""
|
|
234
|
+
Rayleigh extinction from `Hansen & Travis (1974)
|
|
235
|
+
<http://cdsads.u-strasbg.fr/abs/1974SSRv...16..527H>`_.
|
|
236
|
+
|
|
237
|
+
:param lbda: wavelength vector [Å]
|
|
238
|
+
:param pressure: effective surface pressure [mbar]
|
|
239
|
+
:return: Rayleigh extinction [mag/airmass]
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
lm = lbda * 1e-4 # Wavelength from Å to microns
|
|
243
|
+
|
|
244
|
+
# Optical depth
|
|
245
|
+
tau = 0.008569 / lm**4 * (1 + 0.0113 / lm**2 + 0.00013 / lm**4)
|
|
246
|
+
tau *= pressure / 1013.25
|
|
247
|
+
|
|
248
|
+
return tau / EXT2OPT # Convert to attenuation [mag/airmass]
|
|
249
|
+
|
|
250
|
+
def write(self, outname, ext=None, format='txt'):
|
|
251
|
+
"""
|
|
252
|
+
Write extinction curve in output file.
|
|
253
|
+
|
|
254
|
+
:param outname: output filename
|
|
255
|
+
:param ext: explicit extinction curve(s) to be written out
|
|
256
|
+
:param format: output file format ('txt' or 'fits')
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
if ext is None:
|
|
260
|
+
ext = self.extinction(components=True)
|
|
261
|
+
|
|
262
|
+
if format == 'txt': # ASCII table
|
|
263
|
+
ext = np.absolute(ext.round(6)) # Avoid rounding imprecisions
|
|
264
|
+
|
|
265
|
+
outFile = open(outname, 'w')
|
|
266
|
+
# Header
|
|
267
|
+
outFile.write('\n# '.join([''] + str(self).split('\n')) + '\n')
|
|
268
|
+
outFile.write('# Reference: Buton et al. (2013A&A...549A...8B)\n')
|
|
269
|
+
outFile.write('# Wavelength in Å\n')
|
|
270
|
+
outFile.write('# Extinctions in mag/airmass\n')
|
|
271
|
+
outFile.write('# lbda Ext dExt Ray O3 Aero \n')
|
|
272
|
+
# Values
|
|
273
|
+
for l, e, de, r, o, a in zip(self.lbda,
|
|
274
|
+
ext[0], ext[1], ext[2], ext[3], ext[4]):
|
|
275
|
+
outFile.write(' %5d %.3f %.3f %.3f %.3f %.3f\n' % (l, e, de, r, o, a))
|
|
276
|
+
outFile.close()
|
|
277
|
+
|
|
278
|
+
elif format == 'fits': # FITS table
|
|
279
|
+
|
|
280
|
+
p, o3, tau, ang = self.p
|
|
281
|
+
dp, do3, dtau, dang = self.dp
|
|
282
|
+
|
|
283
|
+
keywords = [
|
|
284
|
+
# Generic keywords
|
|
285
|
+
('EXTMODEL', "Rayleigh+Ozone+Aerosols", "Extinction model"),
|
|
286
|
+
('EXTREF',
|
|
287
|
+
"Buton et al., 2013A&A...549A...8B", "Bibliographical ref."),
|
|
288
|
+
# Extinction parameters and errors
|
|
289
|
+
('RA_P', p, "Surface pressure [mbar]"),
|
|
290
|
+
('RA_DP', dp, "Pressure stddev [mbar]"),
|
|
291
|
+
('OZ_INT', o3, "Ozone intensity [DU]"),
|
|
292
|
+
('OZ_DINT', do3, "Ozone intensity stddev [DU]"),
|
|
293
|
+
('AE_TAU', tau, "Aerosol optical depth"),
|
|
294
|
+
('AE_DTAU', dtau, "Aerosol optical depth stddev"),
|
|
295
|
+
('AE_ANG', ang, "Aerosol Angstrom exponent"),
|
|
296
|
+
('AE_DANG', dang, "Aerosol Angstrom exponent stddev"),
|
|
297
|
+
('AE_LREF', self.lrefAero, "Aerosol ref. wavelength [Angstrom]"),
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
# Extinction table
|
|
301
|
+
arrays = [self.lbda, ext[0], ext[1], ext[2], ext[3], ext[4]]
|
|
302
|
+
names = ['LAMBDA', 'EXT', 'DEXT', 'RAYLEIGH', 'OZONE', 'AEROSOLS']
|
|
303
|
+
units = ['Angstrom'] + ['mag/airmass']*5
|
|
304
|
+
|
|
305
|
+
# Extinction BinTableHDU, with keywords
|
|
306
|
+
table = createTable(arrays, names, units=units, keywords=keywords,
|
|
307
|
+
extname='EXTINCTION')
|
|
308
|
+
table.writeto(outname, overwrite=True)
|
|
309
|
+
|
|
310
|
+
else:
|
|
311
|
+
raise ValueError(f"Unknown output format {format!r}.")
|
|
312
|
+
|
|
313
|
+
def plot(self, ext=None, ax=None, components=True, transmission=False):
|
|
314
|
+
"""
|
|
315
|
+
Plot the atmospheric extinction/transmission and its physical components.
|
|
316
|
+
|
|
317
|
+
:param ext: extinctions to be plotted (or None)
|
|
318
|
+
:param ax: matplotlib Axes instance (or None)
|
|
319
|
+
:param components: display individual components if True
|
|
320
|
+
:param transmission: display transmission rather than extinction
|
|
321
|
+
:return: matplotlib Axes instance
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
if ext is None:
|
|
325
|
+
ext = self.extinction(components=True)
|
|
326
|
+
|
|
327
|
+
p, o3, tau, ang = self.p
|
|
328
|
+
|
|
329
|
+
# Non-default colors
|
|
330
|
+
blue, red, green, orange = ('#0066CC', '#CC0033', '#009966', '#FF9900')
|
|
331
|
+
|
|
332
|
+
if transmission: # Extinction [mag/airmass] -> Transmission
|
|
333
|
+
title = "Atmospheric transmission"
|
|
334
|
+
ylbl = "Transmission"
|
|
335
|
+
ext[0] = 10**(-0.4 * ext[0]) # Total transmission
|
|
336
|
+
ext[1] *= -EXT2OPT * ext[0] # Error on total transmission
|
|
337
|
+
ext[2:] = 10**(-0.4 * ext[2:]) # Component transmissions
|
|
338
|
+
else:
|
|
339
|
+
title = "Atmospheric extinction"
|
|
340
|
+
ylbl = "Extinction [mag/airmass]"
|
|
341
|
+
title += " (Buton et al., 2013A&A...549A...8B)"
|
|
342
|
+
|
|
343
|
+
if ax is None: # Create a default axes
|
|
344
|
+
fig = plt.figure(figsize=(8, 5))
|
|
345
|
+
ax = fig.add_subplot(1, 1, 1,
|
|
346
|
+
title=title,
|
|
347
|
+
xlabel="Wavelength [Å]",
|
|
348
|
+
xlim=(self.lbda[0], self.lbda[-1]),
|
|
349
|
+
ylabel=ylbl)
|
|
350
|
+
ax.ticklabel_format(style='plain')
|
|
351
|
+
|
|
352
|
+
# Total extinction and errorband
|
|
353
|
+
ax.plot(self.lbda, ext[0], color=green, lw=2, label='Total')
|
|
354
|
+
ax.fill_between(self.lbda, ext[0] - ext[1], ext[0] + ext[1],
|
|
355
|
+
alpha=0.3, fc=green, ec=green, label='_')
|
|
356
|
+
|
|
357
|
+
if components: # Physical components
|
|
358
|
+
ax.plot(self.lbda, ext[2], color=red, ls='--',
|
|
359
|
+
label=f'Rayleigh [{p:.0f} mbar]')
|
|
360
|
+
ax.plot(self.lbda, ext[3], color=blue, ls=':',
|
|
361
|
+
label=f'Ozone [{o3:.0f} DU]')
|
|
362
|
+
ax.plot(self.lbda, ext[4], color=orange, ls='-.',
|
|
363
|
+
label=f'Aerosols [τ={tau:.4f}, å={ang:.2f}]')
|
|
364
|
+
|
|
365
|
+
ax.legend(loc='best', frameon=False)
|
|
366
|
+
|
|
367
|
+
return ax
|
|
368
|
+
|
|
369
|
+
# Functions ==================================================================
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def readOzoneTemplate(ozoneName, lbda,
|
|
373
|
+
colLbda='LAMBDA', colTrans='OZONE', ext=1):
|
|
374
|
+
"""
|
|
375
|
+
Read ozone transmission template, interpolate over
|
|
376
|
+
wavelengthes, and convert to extinction [mag/airmass].
|
|
377
|
+
|
|
378
|
+
:param ozoneName: input FITS table, with columns *colLbda*
|
|
379
|
+
(wavelength in Å) and *colTrans* (fractional transmission), and
|
|
380
|
+
key 'REFO3COL' specifing the reference ozone column density [DU]
|
|
381
|
+
:param lbda: output wavelengthes [Å]
|
|
382
|
+
:param colLbda: name of the wavelength (in Å) column
|
|
383
|
+
:param colTrans: name of the ozone transmission column
|
|
384
|
+
:param ext: extension in which to look for wavelength and
|
|
385
|
+
transmission columns
|
|
386
|
+
:return: ozone extinction [mag/airmass], refO3col
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
# Read wavelength and transmission columns
|
|
390
|
+
ffile = fits.open(ozoneName)
|
|
391
|
+
x = ffile[ext].data.field(colLbda) # Wavelength
|
|
392
|
+
y = ffile[ext].data.field(colTrans) # Transmission
|
|
393
|
+
refO3col = ffile[ext].header["REFO3COL"]
|
|
394
|
+
|
|
395
|
+
# Interpolate transmissions over lbda
|
|
396
|
+
trans = UnivariateSpline(x, y, s=0)(lbda)
|
|
397
|
+
|
|
398
|
+
# Convert to extinction [mag/airmass]
|
|
399
|
+
return np.absolute(-2.5 * np.log10(trans)), refO3col
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def createTable(arrays, names,
|
|
403
|
+
units=None, formats=None, keywords=(), extname=None):
|
|
404
|
+
"""
|
|
405
|
+
Create a FITS-table from a set of arrays.
|
|
406
|
+
|
|
407
|
+
:param arrays: list of input arrays (ncols,)
|
|
408
|
+
:param names: list of column names (ncols,)
|
|
409
|
+
:param units: list of column units (ncols,) ('none' by default)
|
|
410
|
+
:param formats: list of column formats (ncols,) ('1E' by default)
|
|
411
|
+
:param keywords: list of keys (key,val[,comment]) to add to table header
|
|
412
|
+
:param extname: name of the binary table extension
|
|
413
|
+
:return: table HDU
|
|
414
|
+
"""
|
|
415
|
+
|
|
416
|
+
assert len(arrays) == len(names)
|
|
417
|
+
for arr in arrays[1:]:
|
|
418
|
+
assert len(arr) == len(arrays[0])
|
|
419
|
+
if units is None:
|
|
420
|
+
units = ['none'] * len(arrays)
|
|
421
|
+
else:
|
|
422
|
+
assert len(units) == len(arrays)
|
|
423
|
+
if formats is None:
|
|
424
|
+
formats = ['1E'] * len(arrays)
|
|
425
|
+
else:
|
|
426
|
+
assert len(formats) == len(arrays)
|
|
427
|
+
|
|
428
|
+
cols = [ fits.Column(array=arrays[i], name=names[i], unit=units[i], format=formats[i])
|
|
429
|
+
for i in range(len(arrays)) ]
|
|
430
|
+
|
|
431
|
+
thdu = fits.BinTableHDU.from_columns(cols)
|
|
432
|
+
if extname is not None:
|
|
433
|
+
thdu.header['EXTNAME'] = extname
|
|
434
|
+
thdu.header['FCLASS'] = (26, 'Table file class')
|
|
435
|
+
for key, val, cmt in keywords: # Add some keywords if any
|
|
436
|
+
thdu.header[key] = (val, cmt)
|
|
437
|
+
|
|
438
|
+
return thdu
|
|
Binary file
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pyExtinction
|
|
3
|
+
Version: 2.0
|
|
4
|
+
Summary: Atmospheric extinction, from Buton et al. 2013A&A...549A...8B
|
|
5
|
+
Project-URL: repository, https://gitlab.in2p3.fr/ycopin/pyExtinction
|
|
6
|
+
Project-URL: documentation, https://pyextinction.readthedocs.io/en/latest/
|
|
7
|
+
Author-email: Clément Buton <c.buton@araiko.ai>, Yannick Copin <y.copin@ip2i.in2p3.fr>
|
|
8
|
+
Maintainer-email: Yannick Copin <y.copin@ipnl.in2p3.fr>
|
|
9
|
+
License-Expression: CECILL-C
|
|
10
|
+
License-File: LICENSE.txt
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Requires-Dist: astropy>=6
|
|
13
|
+
Requires-Dist: matplotlib>=3
|
|
14
|
+
Requires-Dist: numpy>=2
|
|
15
|
+
Requires-Dist: scipy>=1
|
|
16
|
+
Description-Content-Type: text/plain
|
|
17
|
+
|
|
18
|
+
======================================================================
|
|
19
|
+
pyExtinction's documentation
|
|
20
|
+
======================================================================
|
|
21
|
+
|
|
22
|
+
:Authors: Clément Buton, Yannick Copin
|
|
23
|
+
:Contact: :email:`Yannick Copin <y.copin@ip2i.in2p3.fr>`
|
|
24
|
+
:Copyright: 2012-present, Clément Buton, Yannick Copin
|
|
25
|
+
|
|
26
|
+
.. only:: html
|
|
27
|
+
|
|
28
|
+
.. image:: https://img.shields.io/badge/ascl-1403.002-blue.svg?colorB=262255
|
|
29
|
+
:target: http://ascl.net/1403.002
|
|
30
|
+
:alt: ascl:1403.002
|
|
31
|
+
|
|
32
|
+
.. image:: https://img.shields.io/pypi/v/pyextinction
|
|
33
|
+
:target: https://pypi.org/project/pyextinction
|
|
34
|
+
:alt: pypi version
|
|
35
|
+
|
|
36
|
+
.. image:: https://img.shields.io/pypi/pyversions/pyextinction
|
|
37
|
+
:target: https://pypi.org/project/pyextinction
|
|
38
|
+
:alt: Supported Python Versions
|
|
39
|
+
|
|
40
|
+
.. figure:: https://readthedocs.org/projects/pyextinction/badge/?version=latest
|
|
41
|
+
:target: http://pyextinction.readthedocs.org/en/latest/?badge=latest
|
|
42
|
+
:alt: pyExtinction Documentation Status
|
|
43
|
+
|
|
44
|
+
Introduction
|
|
45
|
+
============
|
|
46
|
+
|
|
47
|
+
This Python :ref:`script <script>` / :ref:`package <module>` computes total
|
|
48
|
+
atmospheric extinction from decomposition into physical components (Rayleigh
|
|
49
|
+
attenuation, ozone absorption, aerosol extinction), as described in `Buton et
|
|
50
|
+
al. (2013) <http://adsabs.harvard.edu/abs/2013A%26A...549A...8B>`_,
|
|
51
|
+
*Atmospheric extinction properties above Mauna Kea from the Nearby Supernova
|
|
52
|
+
Factory spectro-photometric data set*.
|
|
53
|
+
|
|
54
|
+
Installation
|
|
55
|
+
------------
|
|
56
|
+
|
|
57
|
+
The module is installable from `pypi <https://pypi.org/project/pyextinction/>`_
|
|
58
|
+
(traditional dependencies: numpy_, scipy_, matplotlib_, astropy_)::
|
|
59
|
+
|
|
60
|
+
pip install pyextinction
|
|
61
|
+
|
|
62
|
+
Extinction parameters
|
|
63
|
+
---------------------
|
|
64
|
+
|
|
65
|
+
:ref:`atmosphericExtinction.py <script>` default extinction parameters are
|
|
66
|
+
adapted to mean `Mauna-Kea summit <http://www.ifa.hawaii.edu/mko/>`_
|
|
67
|
+
conditions:
|
|
68
|
+
|
|
69
|
+
.. table:: Atmospheric extinction parameters.
|
|
70
|
+
|
|
71
|
+
================================= =================
|
|
72
|
+
Parameter Value ± Error
|
|
73
|
+
================================= =================
|
|
74
|
+
*Mauna Kea*
|
|
75
|
+
----------------------------------------------------
|
|
76
|
+
Pressure 616 ± 2 mbar
|
|
77
|
+
Ozone column 257 ± 23 DU
|
|
78
|
+
Aerosols optical depth @ 1 µm 0.0076 ± 0.0014
|
|
79
|
+
Aerosols angstrom exponent 1.26 ± 1.33
|
|
80
|
+
================================= =================
|
|
81
|
+
|
|
82
|
+
.. figure:: atmosphericExtinction.*
|
|
83
|
+
:width: 80%
|
|
84
|
+
|
|
85
|
+
Please :email:`contact us <y.copin@ip2i.in2p3.fr>` if you have derived mean
|
|
86
|
+
extinction parameters for other locations.
|
|
87
|
+
|
|
88
|
+
Citations
|
|
89
|
+
---------
|
|
90
|
+
|
|
91
|
+
If you have found this extinction curve model useful for your research, we
|
|
92
|
+
would appreciate a reference to `Buton et al. (2013)
|
|
93
|
+
<http://adsabs.harvard.edu/abs/2013A%26A...549A...8B>`_. The software itself
|
|
94
|
+
can be cited with `Buton & Copin (2014)
|
|
95
|
+
<http://adsabs.harvard.edu/abs/2014ascl.soft03002B>`_.
|
|
96
|
+
|
|
97
|
+
Downloads
|
|
98
|
+
---------
|
|
99
|
+
|
|
100
|
+
* Mauna-Kea mean atmospheric extinction:
|
|
101
|
+
:download:`txt <atmosphericExtinction.txt>`,
|
|
102
|
+
:download:`fits <atmosphericExtinction.fits>`
|
|
103
|
+
* Latest version of the software is available from `gitlab repository
|
|
104
|
+
<https://gitlab.in2p3.fr/ycopin/pyExtinction>`_
|
|
105
|
+
* On-line documentation can be found on `gitlab pages
|
|
106
|
+
<https://ycopin.pages.in2p3.fr/pyExtinction>`_ (latest) and `rtfd
|
|
107
|
+
<http://pyextinction.readthedocs.org/en/latest/>`_ (legacy)
|
|
108
|
+
|
|
109
|
+
Licence
|
|
110
|
+
-------
|
|
111
|
+
|
|
112
|
+
This code is placed under the `CeCILL-C free software license agreement
|
|
113
|
+
<http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html>`_.
|
|
114
|
+
|
|
115
|
+
Some external links cited in the paper (potentially deprecated)
|
|
116
|
+
===============================================================
|
|
117
|
+
|
|
118
|
+
* SkyProbe_
|
|
119
|
+
* `Mauna Loa Observatory`_
|
|
120
|
+
* `Total Ozone measurements`_
|
|
121
|
+
|
|
122
|
+
.. _numpy: https://numpy.org/
|
|
123
|
+
.. _scipy: https://www.scipy.org/
|
|
124
|
+
.. _matplotlib: https://matplotlib.org/
|
|
125
|
+
.. _astropy: https://www.astropy.org/
|
|
126
|
+
.. _SkyProbe: http://www.cfht.hawaii.edu/Instruments/Elixir/skyprobe/home.html
|
|
127
|
+
.. _Mauna Loa Observatory: http://www.esrl.noaa.gov/gmd/obop/mlo/
|
|
128
|
+
.. _Total Ozone measurements: http://www.esrl.noaa.gov/gmd/ozwv/dobson/
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
pyextinction/__init__.py,sha256=ERIRIjXncRHDxppqB6UKlYnDy-ZDu6oWEgqzyTjVkPs,391
|
|
2
|
+
pyextinction/__main__.py,sha256=q-zC7pGEx971fEeuCcQENBrgmlnRGajtXMIjWVwEo9A,5510
|
|
3
|
+
pyextinction/__main__.py.bak,sha256=tJI-Kb3YI6NKzC4HSecE_SJt1vQyTTzQXEPynE5lTis,4750
|
|
4
|
+
pyextinction/atmospheric_extinction.py,sha256=ZuVEnOsQqD2D-a2oUjEezbZyDwifBve_9hcS90x1SPU,16538
|
|
5
|
+
pyextinction/data/ozoneTemplate.fits,sha256=dLMJdBS-AAvUqPZMCRREyIvkZ_ICDUUjixLhkYirlzE,11520
|
|
6
|
+
pyextinction-2.0.dist-info/METADATA,sha256=yOZQ1FnVdEF40CTK-NoPFO291685doUoQniTCup-4zs,4712
|
|
7
|
+
pyextinction-2.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
8
|
+
pyextinction-2.0.dist-info/entry_points.txt,sha256=pNSSqiIH3pDBhPLhbYju3DY2eo-J2mm6hRs5Fj8msao,60
|
|
9
|
+
pyextinction-2.0.dist-info/licenses/LICENSE.txt,sha256=tI9dwW8UTNed2q3OPMjvGDd4oLT3WrQ2LhNLNHyiDz0,21863
|
|
10
|
+
pyextinction-2.0.dist-info/RECORD,,
|