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.
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pyextinction = pyextinction.__main__:main