Call teh ATRAN webinterface for the computation of the atmospheric transmittance

This commit is contained in:
Lukas Klass 2020-10-03 18:30:52 +02:00
parent d29ca5a774
commit 009a01597b
3 changed files with 236 additions and 56 deletions

View File

@ -12,6 +12,8 @@ Attributes:
Atmosphere Atmosphere
---------- ----------
This component models the behaviour of an atmosphere which has a spectral transmittance and a spectral emission. This component models the behaviour of an atmosphere which has a spectral transmittance and a spectral emission.
It is possible to read the transmittance of the atmosphere from a CSV file, from an output file of ATRAN or call the webversion of ATRAN to compute the transmission profile.
The atmospheric emission can bei either read from a CSV file or computed as a grey body radiator of a given temperature and emissivity = 1 - transmission.
.. code-block:: xml .. code-block:: xml
@ -21,18 +23,52 @@ This component models the behaviour of an atmosphere which has a spectral transm
<optical_component type="Atmosphere" transmittance="PathToATRANFile" temp="200" temp_unit="K"/> <optical_component type="Atmosphere" transmittance="PathToATRANFile" temp="200" temp_unit="K"/>
.. code-block:: xml
<optical_component type="Atmosphere" altitude="41000" altitude_unit="ft" wl_min="16" wl_min_unit="um"
wl_max="24" wl_max_unit="um" latitude="39" latitude_unit="degree" water_vapor="0"
water_vapor_unit="um" n_layers="2" zenith_angle="0" zenith_angle_unit="degree" resolution="0"
temp="240" temp_unit="K"/>
Attributes: Attributes:
* | **transmittance:** str * | **transmittance:** str
| The path to the file containing the spectral transmittance coefficients. For details on the required file structure see also :ref:`reading_csv`. | The path to the file containing the spectral transmittance coefficients. For details on the required file structure see also :ref:`reading_csv`.
* | **atran:** str * | **atran:** str
| Path to a file containing the output of ATRAN. In this case, the parameter emission is not available. Instead the parameter temp is used for the atmospheric emission. | Path to a file containing the output of ATRAN.
* | **altitude:** float
| The observatory altitude for the call to ATRAN.
* | **altitude_unit:** str, *optional* = "ft"
| The unit of the observatory altitude for the call to ATRAN.
* | **wl_min:** float
| The minimum wavelength for the call to ATRAN.
* | **wl_min_unit:** str, *optional* = "um"
| The unit of the minimum wavelength for the call to ATRAN.
* | **wl_max:** float
| The maximum wavelength for the call to ATRAN.
* | **wl_max_unit:** str, *optional* = "um"
| The unit of the maximum wavelength for the call to ATRAN.
* | **latitude:** float, *optional*
| The observatory latitude for the call to ATRAN.
* | **latitude_unit:** str, *optional* = "degree"
| The unit of the observatory latitude for the call to ATRAN.
* | **water_vapor:** float, *optional*
| The water vapor overburden for the call to ATRAN.
* | **water_vapor_unit:** str, *optional* = "um"
| The unit of the water vapor overburden for the call to ATRAN.
* | **n_layers:** float, *optional*
| The number of atmospheric layers for the call to ATRAN.
* | **zenith_angle:** float, *optional*
| The zenith angle for the call to ATRAN.
* | **zenith_angle_unit:** str, *optional* = "degree"
| The unit of the zenith angle for the call to ATRAN (0 is towards the zenith).
* | **resolution:** float, *optional*
| The resolution for smoothing for the call to ATRAN (0 for no smoothing).
* | **emission:** str, *optional* * | **emission:** str, *optional*
| The path to the file containing the spectral radiance of the emission. For details on the required file structure see also :ref:`reading_csv`. This option is only available, if the parameter transmittance is given. | The path to the file containing the spectral radiance of the emission. For details on the required file structure see also :ref:`reading_csv`.
* | **temp:** float, *optional* * | **temp:** float, *optional*
| The atmospheric temperature used for black body emission (only available for an ATRAN input). | The atmospheric temperature used for grey body emission.
* | **temp_unit:** str, *optional* = "K" * | **temp_unit:** str, *optional* = "K"
| Unit of the atmospheric temperature used for black body emission using the complement of the ATRAN tranmittance. | Unit of the atmospheric temperature used for black body emission using the complement of the transmittance.
StrayLight StrayLight
---------- ----------

View File

@ -7,6 +7,9 @@ import astropy.units as u
from astropy.io import ascii from astropy.io import ascii
from astropy.modeling.models import BlackBody from astropy.modeling.models import BlackBody
from typing import Union from typing import Union
import re
import requests as req
import numpy as np
class Atmosphere(AOpticalComponent): class Atmosphere(AOpticalComponent):
@ -14,6 +17,9 @@ class Atmosphere(AOpticalComponent):
A class to model the atmosphere including the atmosphere's spectral transmittance and emission. A class to model the atmosphere including the atmosphere's spectral transmittance and emission.
""" """
# defining the ATRAN-endpoint
ATRAN = "https://atran.arc.nasa.gov"
def __init__(self, **kwargs): def __init__(self, **kwargs):
""" """
Initialize a new atmosphere model Initialize a new atmosphere model
@ -27,6 +33,22 @@ class Atmosphere(AOpticalComponent):
The format of the file will be guessed by `astropy.io.ascii.read()`. The format of the file will be guessed by `astropy.io.ascii.read()`.
atran : str atran : str
Path to the ATRAN output file containing the spectral transmittance-coefficients of the atmosphere. Path to the ATRAN output file containing the spectral transmittance-coefficients of the atmosphere.
altitude : u.Quantity
The observatory altitude in feet.
wl_min : u.Quantity
The minimal wavelength to consider in micrometer.
wl_max : u.Quantity
The maximal wavelength to consider in micrometer.
latitude : u.Quantity
The observatory's latitude in degrees.
water_vapor : u.Quantity
The water vapor overburden in microns (0 if unknown).
n_layers : int
The number of considered atmopsheric layers.
zenith_angle : u.Quantity
The zenith angle of the observation in degrees (0 is towards the zenith).
resolution : int
The resolution for smoothing (0 for no smoothing).
emission : str emission : str
Path to the file containing the spectral radiance of the atmosphere. Path to the file containing the spectral radiance of the atmosphere.
The format of the file will be guessed by `astropy.io.ascii.read()`. The format of the file will be guessed by `astropy.io.ascii.read()`.
@ -36,14 +58,28 @@ class Atmosphere(AOpticalComponent):
args = dict() args = dict()
if "atran" in kwargs: if "atran" in kwargs:
args = self._fromATRAN(**kwargs) args = self._fromATRAN(**{x: kwargs[x] for x in kwargs.keys() if x not in ["emission", "temp"]})
elif "altitude" in kwargs:
logger.info("Requesting ATRAN transmission profile.")
data = self.__call_ATRAN(**{x: kwargs[x] for x in kwargs.keys() if x not in ["parent", "temp"]})
args = self._fromATRAN(parent=kwargs["parent"], atran=data)
elif "transmittance" in kwargs: elif "transmittance" in kwargs:
args = self._fromFiles(**kwargs) args = self._fromFiles(**{x: kwargs[x] for x in kwargs.keys() if x not in ["emission", "temp"]})
else: else:
logger.error("Wrong parameters for class Atmosphere.") logger.error("Wrong parameters for class Atmosphere.")
if "temp" in kwargs:
# Create black body
bb = self.__gb_factory(kwargs["temp"])
# Calculate emission
args["emission"] = SpectralQty(args["transmittance"].wl, bb(args["transmittance"].wl)) * (
-1 * args["transmittance"] + 1)
elif "emission" in kwargs:
args["emission"] = SpectralQty.fromFile(kwargs["emission"], wl_unit_default=u.nm,
qty_unit_default=u.W / (u.m ** 2 * u.nm * u.sr))
super().__init__(parent=args["parent"], transreflectivity=args["transmittance"], noise=args["emission"]) super().__init__(parent=args["parent"], transreflectivity=args["transmittance"], noise=args["emission"])
def _fromFiles(self, parent: IRadiant, transmittance: str, emission: str = None): def _fromFiles(self, parent: IRadiant, transmittance: str):
""" """
Initialize a new atmosphere model from two files Initialize a new atmosphere model from two files
@ -54,9 +90,6 @@ class Atmosphere(AOpticalComponent):
transmittance : str transmittance : str
Path to the file containing the spectral transmittance-coefficients of the atmosphere. Path to the file containing the spectral transmittance-coefficients of the atmosphere.
The format of the file will be guessed by `astropy.io.ascii.read()`. The format of the file will be guessed by `astropy.io.ascii.read()`.
emission : str
Path to the file containing the spectral radiance of the atmosphere.
The format of the file will be guessed by `astropy.io.ascii.read()`.
Returns Returns
------- -------
@ -66,14 +99,9 @@ class Atmosphere(AOpticalComponent):
# Read the transmittance # Read the transmittance
transmittance = SpectralQty.fromFile(transmittance, wl_unit_default=u.nm, transmittance = SpectralQty.fromFile(transmittance, wl_unit_default=u.nm,
qty_unit_default=u.dimensionless_unscaled) qty_unit_default=u.dimensionless_unscaled)
if emission is None: return {"parent": parent, "transmittance": transmittance}
emission = 0
else:
emission = SpectralQty.fromFile(emission, wl_unit_default=u.nm,
qty_unit_default=u.W / (u.m ** 2 * u.nm * u.sr))
return {"parent": parent, "transmittance": transmittance, "emission": emission}
def _fromATRAN(self, parent: IRadiant, atran: str, temp: u.Quantity = None): def _fromATRAN(self, parent: IRadiant, atran: str):
""" """
Initialize a new atmosphere model from an ATRAN output file Initialize a new atmosphere model from an ATRAN output file
@ -83,8 +111,6 @@ class Atmosphere(AOpticalComponent):
The parent element of the atmosphere from which the electromagnetic radiation is received. The parent element of the atmosphere from which the electromagnetic radiation is received.
atran : str atran : str
Path to the ATRAN output file containing the spectral transmittance-coefficients of the atmosphere. Path to the ATRAN output file containing the spectral transmittance-coefficients of the atmosphere.
temp : u.Quantity
The atmospheric temperature for the atmosphere's black body radiation.
Returns Returns
------- -------
@ -92,54 +118,109 @@ class Atmosphere(AOpticalComponent):
The arguments for the class instantiation. The arguments for the class instantiation.
""" """
# Read the file # Read the file
data = ascii.read(atran, format=None) data = self.__parse_ATRAN(atran)
# Set units
data["col2"].unit = u.um
data["col3"].unit = u.dimensionless_unscaled
# Create spectral quantity # Create spectral quantity
transmittance = SpectralQty(data["col2"].quantity, data["col3"].quantity) transmittance = SpectralQty(data["col2"].quantity, data["col3"].quantity)
return {"parent": parent, "transmittance": transmittance}
if temp is not None: @u.quantity_input(altitude="length", latitude="angle", water_vapor="length", zenith_angle="angle", wl_min="length",
# Create black body wl_max="length")
bb = self.__gb_factory(temp) def __call_ATRAN(self, altitude: u.Quantity, wl_min: u.Quantity, wl_max: u.Quantity,
# Calculate emission latitude: u.Quantity = 39 * u.degree, water_vapor: u.Quantity = 0 * u.um, n_layers: int = 2,
emission = SpectralQty(transmittance.wl, bb(transmittance.wl)) * transmittance zenith_angle: u.Quantity = 0 * u.degree, resolution: int = 0):
else:
emission = 0
return {"parent": parent, "transmittance": transmittance, "emission": emission}
@staticmethod
def check_config(conf: Entry) -> Union[None, str]:
""" """
Check the configuration for this class Call the online version of ATRAN provided by SOFIA
Parameters Parameters
---------- ----------
conf : Entry altitude : u.Quantity
The configuration entry to be checked. The observatory altitude in feet.
wl_min : u.Quantity
The minimal wavelength to consider in micrometer.
wl_max : u.Quantity
The maximal wavelength to consider in micrometer.
latitude : u.Quantity
The observatory's latitude in degrees.
water_vapor : u.Quantity
The water vapor overburden in microns (0 if unknown).
n_layers : int
The number of considered atmopsheric layers.
zenith_angle : u.Quantity
The zenith angle of the observation in degrees (0 is towards the zenith).
resolution : int
The resolution for smoothing (0 for no smoothing).
Returns Returns
------- -------
mes : Union[None, str] data : str
The error message of the check. This will be None if the check was successful. The ATRAN computation results
""" """
if hasattr(conf, "transmittance"): # Select closest latitude from ATRAN options
mes = conf.check_file("transmittance") latitude_ = min(np.array([9, 30, 39, 43, 59]) * u.degree, key=lambda x: abs(x - latitude.to(u.degree)))
if mes is not None: # Select closest number of layers from ATRAN options
return mes n_layers_ = min([2, 3, 4, 5], key=lambda x: abs(x - n_layers))
if hasattr(conf, "emission"): # Assemble the data payload
mes = conf.check_file("emission") data = {'Altitude': altitude.to(u.imperial.ft).value,
if mes is not None: 'Obslat': '%d deg' % latitude_.value,
return mes 'WVapor': water_vapor.to(u.um).value,
else: 'NLayers': n_layers_,
mes = conf.check_file("atran") 'ZenithAngle': zenith_angle.to(u.degree).value,
if mes is not None: 'WaveMin': wl_min.to(u.um).value,
return mes 'WaveMax': wl_max.to(u.um).value,
if hasattr(conf, "temp"): 'Resolution': resolution}
mes = conf.check_quantity("temp", u.K) # Send data to ATRAN via POST request
if mes is not None: res = req.post(url=self.ATRAN + "/cgi-bin/atran/atran.cgi", data=data)
return mes # Check if request was successful
if not res.ok:
logger.error("Error: Request returned status code " + str(res.status_code))
# Extract the content of the reply
content = res.text
# Check if any ATRAN error occured
match = re.search('<CENTER><H2>ERROR!!</H2></CENTER><CENTER>(.*)</CENTER>', content)
if match:
logger.error("Error: " + match.group(1))
# Extract link to ATRAN result file
match = re.search('href="(/atran_calc/atran.(?:plt|smo).\\d*.dat)"', content)
# Check if link was found
if not match:
logger.error("Error: Link to data file not found.")
# Request the ATRAN result via GET request
res = req.get(self.ATRAN + match.group(1))
# Check if request was successful
if not res.ok:
logger.error("Error: Request returned status code " + str(res.status_code))
# Extract the content of the reply
data = res.text
# Check if result is empty
if data == "":
logger.error("Error: Request returned empty response.")
return data
@staticmethod
def __parse_ATRAN(table: str):
"""
Parse an ATRAN result file and convert it to an astropy table
Parameters
----------
table : str
Path to the file or content of the file.
Returns
-------
data : astropy.Table
The parsed table object.
"""
# Read the file
data = ascii.read(table, format=None)
# Set units
data["col2"].unit = u.um
data["col3"].unit = u.dimensionless_unscaled
return data
@staticmethod @staticmethod
@u.quantity_input(temp=[u.Kelvin, u.Celsius]) @u.quantity_input(temp=[u.Kelvin, u.Celsius])
@ -161,3 +242,65 @@ class Atmosphere(AOpticalComponent):
""" """
bb = BlackBody(temperature=temp, scale=em * u.W / (u.m ** 2 * u.nm * u.sr)) bb = BlackBody(temperature=temp, scale=em * u.W / (u.m ** 2 * u.nm * u.sr))
return lambda wl: bb(wl) return lambda wl: bb(wl)
@staticmethod
def check_config(conf: Entry) -> Union[None, str]:
"""
Check the configuration for this class
Parameters
----------
conf : Entry
The configuration entry to be checked.
Returns
-------
mes : Union[None, str]
The error message of the check. This will be None if the check was successful.
"""
if hasattr(conf, "transmittance"):
mes = conf.check_file("transmittance")
if mes is not None:
return mes
elif hasattr(conf, "atran"):
mes = conf.check_file("atran")
if mes is not None:
return mes
else:
mes = conf.check_quantity("altitude", u.imperial.ft)
if mes is not None:
return mes
mes = conf.check_quantity("wl_min", u.um)
if mes is not None:
return mes
mes = conf.check_quantity("wl_max", u.um)
if mes is not None:
return mes
if hasattr(conf, "latitude"):
mes = conf.check_quantity("latitude", u.degree)
if mes is not None:
return mes
if hasattr(conf, "water_vapor"):
mes = conf.check_quantity("water_vapor", u.um)
if mes is not None:
return mes
if hasattr(conf, "n_layers"):
mes = conf.check_float("n_layers")
if mes is not None:
return mes
if hasattr(conf, "zenith_angle"):
mes = conf.check_quantity("zenith_angle", u.degree)
if mes is not None:
return mes
if hasattr(conf, "resolution"):
mes = conf.check_float("resolution")
if mes is not None:
return mes
if hasattr(conf, "emission"):
mes = conf.check_file("emission")
if mes is not None:
return mes
elif hasattr(conf, "temp"):
mes = conf.check_quantity("temp", u.K)
if mes is not None:
return mes

View File

@ -7,4 +7,5 @@ halo~=0.0.29
pyfiglet~=0.8.post1 pyfiglet~=0.8.post1
Sphinx~=3.1.2 Sphinx~=3.1.2
sphinx-rtd-theme~=0.5.0 sphinx-rtd-theme~=0.5.0
requests~=2.24.0