Move ATRAN implementation to separate class
This commit is contained in:
parent
32a0885342
commit
e1a78f053b
@ -12,13 +12,34 @@ 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 transmittance is read from a CSV file.
|
||||||
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.
|
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
|
||||||
|
|
||||||
<optical_component type="Atmosphere" transmittance="PathToTransmittanceFile" emission="PathToEmissionFile"/>
|
<optical_component type="Atmosphere" transmittance="PathToTransmittanceFile" emission="PathToEmissionFile"/>
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<optical_component type="Atmosphere" transmittance="PathToTransmittanceFile" temp="200" temp_unit="K"/>
|
||||||
|
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
* | **transmittance:** str
|
||||||
|
| The path to the file containing the spectral transmittance coefficients. For details on the required file structure see also :ref:`reading_csv`.
|
||||||
|
* | **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`.
|
||||||
|
* | **temp:** float, *optional*
|
||||||
|
| The atmospheric temperature used for grey body emission.
|
||||||
|
* | **temp_unit:** str, *optional* = "K"
|
||||||
|
| Unit of the atmospheric temperature used for black body emission using the complement of the transmittance.
|
||||||
|
|
||||||
|
ATRAN
|
||||||
|
----------
|
||||||
|
This component uses the atmospheric transmission calculator ATRAN to model the behaviour of the atmosphere
|
||||||
|
It is possible to read the transmittance of the atmosphere from an output file of ATRAN or call the webversion of ATRAN to compute the transmission profile.
|
||||||
|
The atmospheric emission is computed as a grey body radiator of a given temperature and emissivity = 1 - transmission.
|
||||||
|
|
||||||
.. code-block:: xml
|
.. code-block:: xml
|
||||||
|
|
||||||
<optical_component type="Atmosphere" transmittance="PathToATRANFile" temp="200" temp_unit="K"/>
|
<optical_component type="Atmosphere" transmittance="PathToATRANFile" temp="200" temp_unit="K"/>
|
||||||
@ -32,8 +53,6 @@ The atmospheric emission can bei either read from a CSV file or computed as a gr
|
|||||||
|
|
||||||
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`.
|
|
||||||
* | **atran:** str
|
|
||||||
| Path to a file containing the output of ATRAN.
|
| Path to a file containing the output of ATRAN.
|
||||||
* | **altitude:** float
|
* | **altitude:** float
|
||||||
| The observatory altitude for the call to ATRAN.
|
| The observatory altitude for the call to ATRAN.
|
||||||
@ -63,13 +82,12 @@ Attributes:
|
|||||||
| The unit of the zenith angle for the call to ATRAN (0 is towards the zenith).
|
| The unit of the zenith angle for the call to ATRAN (0 is towards the zenith).
|
||||||
* | **resolution:** float, *optional*
|
* | **resolution:** float, *optional*
|
||||||
| The resolution for smoothing for the call to ATRAN (0 for no smoothing).
|
| The resolution for smoothing for the call to ATRAN (0 for no smoothing).
|
||||||
* | **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`.
|
|
||||||
* | **temp:** float, *optional*
|
* | **temp:** float, *optional*
|
||||||
| The atmospheric temperature used for grey body emission.
|
| 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 transmittance.
|
| Unit of the atmospheric temperature used for black body emission using the complement of the transmittance.
|
||||||
|
|
||||||
|
|
||||||
StrayLight
|
StrayLight
|
||||||
----------
|
----------
|
||||||
This component allows to model generic noise sources like stray light or zodiacal light from a file containing the spectral radiance of the emission.
|
This component allows to model generic noise sources like stray light or zodiacal light from a file containing the spectral radiance of the emission.
|
||||||
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.6 MiB |
@ -134,6 +134,7 @@ components with a thermal grey body emission form a subclass of the optical comp
|
|||||||
components are available
|
components are available
|
||||||
|
|
||||||
* An **atmosphere** component modelling the atmospheric transmittance and emission, both read from files.
|
* An **atmosphere** component modelling the atmospheric transmittance and emission, both read from files.
|
||||||
|
* An **ATRAN** component to model the atmosphere using the ATRAN atmopsheric transmission calculator.
|
||||||
* A **stray light** component for modelling generic background source like zodiacal light or earth stray light.
|
* A **stray light** component for modelling generic background source like zodiacal light or earth stray light.
|
||||||
* A **cosmic background** component to model thermal black body background radiation like the 2.7 K cosmic background radiation.
|
* A **cosmic background** component to model thermal black body background radiation like the 2.7 K cosmic background radiation.
|
||||||
* Hot optical components with thermal emission of a given temperature and emissivity:
|
* Hot optical components with thermal emission of a given temperature and emissivity:
|
||||||
|
246
esbo_etc/classes/optical_component/ATRAN.py
Normal file
246
esbo_etc/classes/optical_component/ATRAN.py
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
from .Atmosphere import Atmosphere
|
||||||
|
from ..IRadiant import IRadiant
|
||||||
|
from ..SpectralQty import SpectralQty
|
||||||
|
from ..Entry import Entry
|
||||||
|
from ...lib.logger import logger
|
||||||
|
from ...lib.cache import cache
|
||||||
|
import astropy.units as u
|
||||||
|
from astropy.io import ascii
|
||||||
|
from astropy.modeling.models import BlackBody
|
||||||
|
from astropy.table import QTable
|
||||||
|
from typing import Union
|
||||||
|
import re
|
||||||
|
import requests as req
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
class ATRAN(Atmosphere):
|
||||||
|
"""
|
||||||
|
A class to model the atmosphere including the atmosphere's spectral transmittance and emission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# defining the ATRAN-endpoint
|
||||||
|
ATRAN = "https://atran.arc.nasa.gov"
|
||||||
|
|
||||||
|
@u.quantity_input(altitude="length", latitude="angle", water_vapor="length", zenith_angle="angle", wl_min="length",
|
||||||
|
wl_max="length", temp=[u.Kelvin, u.Celsius])
|
||||||
|
def __init__(self, parent: IRadiant, transmittance: str = None, altitude: u.Quantity = None,
|
||||||
|
wl_min: u.Quantity = None, wl_max: u.Quantity = None, latitude: u.Quantity = 39 * u.degree,
|
||||||
|
water_vapor: u.Quantity = 0 * u.um, n_layers: int = 2, zenith_angle: u.Quantity = 0 * u.degree,
|
||||||
|
resolution: int = 0, temp: u.Quantity = 0 * u.K):
|
||||||
|
"""
|
||||||
|
Initialize a new atmosphere model from ATRAN output
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
parent : IRadiant
|
||||||
|
The parent element of the atmosphere from which the electromagnetic radiation is received.
|
||||||
|
transmittance : str
|
||||||
|
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).
|
||||||
|
temp : u.Quantity
|
||||||
|
The atmospheric temperature for the atmosphere's black body radiation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if transmittance is not None:
|
||||||
|
data = self.__parse_ATRAN(transmittance)
|
||||||
|
else:
|
||||||
|
logger.info("Requesting ATRAN transmission profile.")
|
||||||
|
data = self.__call_ATRAN(altitude, wl_min, wl_max, latitude, water_vapor, n_layers, zenith_angle,
|
||||||
|
resolution)
|
||||||
|
|
||||||
|
transmittance = SpectralQty(data["col2"].quantity, data["col3"].quantity)
|
||||||
|
super().__init__(parent, transmittance, temp=temp)
|
||||||
|
|
||||||
|
@u.quantity_input(altitude="length", latitude="angle", water_vapor="length", zenith_angle="angle", wl_min="length",
|
||||||
|
wl_max="length")
|
||||||
|
@cache
|
||||||
|
def __call_ATRAN(self, altitude: u.Quantity, wl_min: u.Quantity, wl_max: u.Quantity,
|
||||||
|
latitude: u.Quantity = 39 * u.degree, water_vapor: u.Quantity = 0 * u.um, n_layers: int = 2,
|
||||||
|
zenith_angle: u.Quantity = 0 * u.degree, resolution: int = 0):
|
||||||
|
"""
|
||||||
|
Call the online version of ATRAN provided by SOFIA
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
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).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
data : QTable
|
||||||
|
The ATRAN computation results
|
||||||
|
"""
|
||||||
|
# Select closest latitude from ATRAN options
|
||||||
|
latitude_ = min(np.array([9, 30, 39, 43, 59]) * u.degree, key=lambda x: abs(x - latitude.to(u.degree)))
|
||||||
|
# Select closest number of layers from ATRAN options
|
||||||
|
n_layers_ = min([2, 3, 4, 5], key=lambda x: abs(x - n_layers))
|
||||||
|
# Assemble the data payload
|
||||||
|
data = {'Altitude': altitude.to(u.imperial.ft).value,
|
||||||
|
'Obslat': '%d deg' % latitude_.value,
|
||||||
|
'WVapor': water_vapor.to(u.um).value,
|
||||||
|
'NLayers': n_layers_,
|
||||||
|
'ZenithAngle': zenith_angle.to(u.degree).value,
|
||||||
|
'WaveMin': wl_min.to(u.um).value,
|
||||||
|
'WaveMax': wl_max.to(u.um).value,
|
||||||
|
'Resolution': resolution}
|
||||||
|
# Send data to ATRAN via POST request
|
||||||
|
res = req.post(url=self.ATRAN + "/cgi-bin/atran/atran.cgi", data=data)
|
||||||
|
# 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 self.__parse_ATRAN(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
|
||||||
|
@u.quantity_input(temp=[u.Kelvin, u.Celsius])
|
||||||
|
def __gb_factory(temp: u.Quantity, em: Union[int, float] = 1):
|
||||||
|
"""
|
||||||
|
Factory for a grey body lambda-function.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
temp : Quantity in Kelvin / Celsius
|
||||||
|
The temperature fo the grey body.
|
||||||
|
em : Union[int, float]
|
||||||
|
Emissivity of the the grey body
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bb : Callable
|
||||||
|
The lambda function for the grey body.
|
||||||
|
"""
|
||||||
|
bb = BlackBody(temperature=temp, scale=em * u.W / (u.m ** 2 * u.nm * u.sr))
|
||||||
|
return lambda wl: bb(wl)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "ATRAN Object"
|
||||||
|
|
||||||
|
@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
|
||||||
|
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, "temp"):
|
||||||
|
mes = conf.check_quantity("temp", u.K)
|
||||||
|
if mes is not None:
|
||||||
|
return mes
|
@ -2,16 +2,9 @@ from .AOpticalComponent import AOpticalComponent
|
|||||||
from ..IRadiant import IRadiant
|
from ..IRadiant import IRadiant
|
||||||
from ..SpectralQty import SpectralQty
|
from ..SpectralQty import SpectralQty
|
||||||
from ..Entry import Entry
|
from ..Entry import Entry
|
||||||
from ...lib.logger import logger
|
|
||||||
from ...lib.cache import cache
|
|
||||||
import astropy.units as u
|
import astropy.units as u
|
||||||
from astropy.io import ascii
|
|
||||||
from astropy.modeling.models import BlackBody
|
from astropy.modeling.models import BlackBody
|
||||||
from astropy.table import QTable
|
|
||||||
from typing import Union
|
from typing import Union
|
||||||
import re
|
|
||||||
import requests as req
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
|
|
||||||
class Atmosphere(AOpticalComponent):
|
class Atmosphere(AOpticalComponent):
|
||||||
@ -19,10 +12,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
|
@u.quantity_input(temp=[u.Kelvin, u.Celsius])
|
||||||
ATRAN = "https://atran.arc.nasa.gov"
|
def __init__(self, parent: IRadiant, transmittance: Union[str, float, SpectralQty], emission: str = None,
|
||||||
|
temp: u.Quantity = None):
|
||||||
def __init__(self, **kwargs):
|
|
||||||
"""
|
"""
|
||||||
Initialize a new atmosphere model
|
Initialize a new atmosphere model
|
||||||
|
|
||||||
@ -30,27 +22,9 @@ class Atmosphere(AOpticalComponent):
|
|||||||
----------
|
----------
|
||||||
parent : IRadiant
|
parent : IRadiant
|
||||||
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.
|
||||||
transmittance : str
|
transmittance : str, float, SpectralQty
|
||||||
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()`.
|
||||||
atran : str
|
|
||||||
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()`.
|
||||||
@ -58,173 +32,26 @@ class Atmosphere(AOpticalComponent):
|
|||||||
The atmospheric temperature for the atmosphere's black body radiation.
|
The atmospheric temperature for the atmosphere's black body radiation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
args = dict()
|
|
||||||
if "atran" in kwargs:
|
|
||||||
data = self.__parse_ATRAN(kwargs["atran"])
|
|
||||||
args = self._fromATRAN(parent=kwargs["parent"], atran=data)
|
|
||||||
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:
|
|
||||||
args = self._fromFiles(**{x: kwargs[x] for x in kwargs.keys() if x not in ["emission", "temp"]})
|
|
||||||
else:
|
|
||||||
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))
|
|
||||||
else:
|
|
||||||
args["emission"] = 0
|
|
||||||
super().__init__(parent=args["parent"], transreflectivity=args["transmittance"], noise=args["emission"])
|
|
||||||
|
|
||||||
def _fromFiles(self, parent: IRadiant, transmittance: str):
|
|
||||||
"""
|
|
||||||
Initialize a new atmosphere model from two files
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
parent : IRadiant
|
|
||||||
The parent element of the atmosphere from which the electromagnetic radiation is received.
|
|
||||||
transmittance : str
|
|
||||||
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()`.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
args : dict
|
|
||||||
The arguments for the class instantiation.
|
|
||||||
"""
|
|
||||||
# Read the transmittance
|
# Read the transmittance
|
||||||
transmittance = SpectralQty.fromFile(transmittance, wl_unit_default=u.nm,
|
if isinstance(transmittance, str):
|
||||||
qty_unit_default=u.dimensionless_unscaled)
|
transmittance_sqty = SpectralQty.fromFile(transmittance, wl_unit_default=u.nm,
|
||||||
return {"parent": parent, "transmittance": transmittance}
|
qty_unit_default=u.dimensionless_unscaled)
|
||||||
|
else:
|
||||||
|
transmittance_sqty = transmittance
|
||||||
|
if emission:
|
||||||
|
# Read the emission
|
||||||
|
emission_sqty = SpectralQty.fromFile(emission, wl_unit_default=u.nm,
|
||||||
|
qty_unit_default=u.W / (u.m ** 2 * u.nm * u.sr))
|
||||||
|
elif temp is not None:
|
||||||
|
# Create black body
|
||||||
|
bb = self.__gb_factory(temp)
|
||||||
|
# Calculate emission
|
||||||
|
emission_sqty = SpectralQty(transmittance_sqty.wl, bb(transmittance_sqty.wl)) * (
|
||||||
|
-1 * transmittance_sqty + 1)
|
||||||
|
else:
|
||||||
|
emission_sqty = 0
|
||||||
|
|
||||||
def _fromATRAN(self, parent: IRadiant, atran: QTable):
|
super().__init__(parent=parent, transreflectivity=transmittance_sqty, noise=emission_sqty)
|
||||||
"""
|
|
||||||
Initialize a new atmosphere model from an ATRAN output file
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
parent : IRadiant
|
|
||||||
The parent element of the atmosphere from which the electromagnetic radiation is received.
|
|
||||||
atran : QTable
|
|
||||||
QTable containing the atmospheric transmission coefficients.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
args : dict
|
|
||||||
The arguments for the class instantiation.
|
|
||||||
"""
|
|
||||||
# Create spectral quantity
|
|
||||||
transmittance = SpectralQty(atran["col2"].quantity, atran["col3"].quantity)
|
|
||||||
return {"parent": parent, "transmittance": transmittance}
|
|
||||||
|
|
||||||
@u.quantity_input(altitude="length", latitude="angle", water_vapor="length", zenith_angle="angle", wl_min="length",
|
|
||||||
wl_max="length")
|
|
||||||
@cache
|
|
||||||
def __call_ATRAN(self, altitude: u.Quantity, wl_min: u.Quantity, wl_max: u.Quantity,
|
|
||||||
latitude: u.Quantity = 39 * u.degree, water_vapor: u.Quantity = 0 * u.um, n_layers: int = 2,
|
|
||||||
zenith_angle: u.Quantity = 0 * u.degree, resolution: int = 0):
|
|
||||||
"""
|
|
||||||
Call the online version of ATRAN provided by SOFIA
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
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).
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
data : QTable
|
|
||||||
The ATRAN computation results
|
|
||||||
"""
|
|
||||||
# Select closest latitude from ATRAN options
|
|
||||||
latitude_ = min(np.array([9, 30, 39, 43, 59]) * u.degree, key=lambda x: abs(x - latitude.to(u.degree)))
|
|
||||||
# Select closest number of layers from ATRAN options
|
|
||||||
n_layers_ = min([2, 3, 4, 5], key=lambda x: abs(x - n_layers))
|
|
||||||
# Assemble the data payload
|
|
||||||
data = {'Altitude': altitude.to(u.imperial.ft).value,
|
|
||||||
'Obslat': '%d deg' % latitude_.value,
|
|
||||||
'WVapor': water_vapor.to(u.um).value,
|
|
||||||
'NLayers': n_layers_,
|
|
||||||
'ZenithAngle': zenith_angle.to(u.degree).value,
|
|
||||||
'WaveMin': wl_min.to(u.um).value,
|
|
||||||
'WaveMax': wl_max.to(u.um).value,
|
|
||||||
'Resolution': resolution}
|
|
||||||
# Send data to ATRAN via POST request
|
|
||||||
res = req.post(url=self.ATRAN + "/cgi-bin/atran/atran.cgi", data=data)
|
|
||||||
# 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 self.__parse_ATRAN(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])
|
||||||
@ -247,9 +74,6 @@ 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)
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "Atmosphere Object"
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_config(conf: Entry) -> Union[None, str]:
|
def check_config(conf: Entry) -> Union[None, str]:
|
||||||
"""
|
"""
|
||||||
@ -265,44 +89,9 @@ class Atmosphere(AOpticalComponent):
|
|||||||
mes : Union[None, str]
|
mes : Union[None, str]
|
||||||
The error message of the check. This will be None if the check was successful.
|
The error message of the check. This will be None if the check was successful.
|
||||||
"""
|
"""
|
||||||
if hasattr(conf, "transmittance"):
|
mes = conf.check_file("transmittance")
|
||||||
mes = conf.check_file("transmittance")
|
if mes is not None:
|
||||||
if mes is not None:
|
return mes
|
||||||
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"):
|
if hasattr(conf, "emission"):
|
||||||
mes = conf.check_file("emission")
|
mes = conf.check_file("emission")
|
||||||
if mes is not None:
|
if mes is not None:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from .AOpticalComponent import *
|
from .AOpticalComponent import *
|
||||||
from .Atmosphere import *
|
from .Atmosphere import *
|
||||||
|
from .ATRAN import *
|
||||||
from .StrayLight import *
|
from .StrayLight import *
|
||||||
from .AHotOpticalComponent import *
|
from .AHotOpticalComponent import *
|
||||||
from .Filter import *
|
from .Filter import *
|
||||||
|
10001
tests/data/atmosphere/atran.dat
Normal file
10001
tests/data/atmosphere/atran.dat
Normal file
File diff suppressed because it is too large
Load Diff
30
tests/optical_component/test_ATRAN.py
Normal file
30
tests/optical_component/test_ATRAN.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
from esbo_etc import ATRAN, FileTarget, SpectralQty
|
||||||
|
import numpy as np
|
||||||
|
import astropy.units as u
|
||||||
|
|
||||||
|
|
||||||
|
class TestATRAN(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.target = FileTarget("tests/data/target/target_demo_3.csv", np.arange(16, 16.1, 0.01) << u.um)
|
||||||
|
self.atmosphere = ATRAN(parent=self.target, transmittance="tests/data/atmosphere/atran.dat", temp=240 * u.K)
|
||||||
|
self.atmosphere2 = ATRAN(parent=self.target, altitude=15 * u.km, wl_min=16 * u.um, wl_max=16.1 * u.um,
|
||||||
|
temp=240 * u.K)
|
||||||
|
|
||||||
|
def test_calcSignal(self):
|
||||||
|
self.assertEqual(self.atmosphere.calcSignal()[0],
|
||||||
|
SpectralQty(np.arange(16, 16.1, 0.01) << u.um,
|
||||||
|
np.array([6.555e-16, 1.03e-15, 4.311e-16, 6.13e-16, 1.016e-15, 1.077e-15,
|
||||||
|
4.733e-16, 8.143e-16, 1.12e-15, 1.128e-15, 5.569e-16]) << u.W / (
|
||||||
|
u.m ** 2 * u.nm)))
|
||||||
|
self.assertEqual(self.atmosphere2.calcSignal()[0],
|
||||||
|
SpectralQty(np.arange(16, 16.1, 0.01) << u.um,
|
||||||
|
np.array([8.469e-16, 1.068e-15, 5.145e-16, 8.574e-16, 1.087e-15, 1.113e-15,
|
||||||
|
5.08e-16, 9.303e-16, 1.15e-15, 1.107e-15, 0]) << u.W / (
|
||||||
|
u.m ** 2 * u.nm)))
|
||||||
|
|
||||||
|
def test_calcBackground(self):
|
||||||
|
self.assertTrue(np.allclose(self.atmosphere.calcBackground().qty,
|
||||||
|
np.array([1.109e-3, 1.97e-4, 1.686e-3, 1.253e-3, 2.989e-4, 1.741e-4, 1.618e-3,
|
||||||
|
8.301e-4, 1.377e-4, 1.431e-4, 1.46e-3]) << u.W / (
|
||||||
|
u.m ** 2 * u.nm * u.sr), atol=1e-6))
|
Loading…
x
Reference in New Issue
Block a user