Factories unified

This commit is contained in:
Lukas Klass 2020-09-12 11:51:49 +02:00
parent 8aab5f2f24
commit f951f5c636
7 changed files with 76 additions and 97 deletions

View File

@ -34,7 +34,7 @@ The method ``propagate()`` is used to model the propagation of incoming radiatio
Therefore, this method receives as parameter the incoming radiation as ``SpectralQty``-object and must return the manipulated radiation as ``SpectralQty``-object.
The optional second task consists of modifying the factory method ``create()`` of the class ``OpticalComponentFactory`` in order to properly initialize the new optical component from the configuration.
This is only necessary if the new optical component needs additional parameters besides the attributes of the corresponding configuration tag of if the new component provides multiple constructors (see class ``Filter``).
This is only necessary if the new optical component needs additional parameters besides the attributes of the corresponding configuration tag.
Adding Detector Components
--------------------------
@ -46,5 +46,4 @@ Thereby, the new class must implement the three methods ``calcSNR()``, ``calcExp
All three methods obtain the incoming background and signal radiation as well as the obstruction factor as parameters apart from some specific parameters and must return the corresponding calculated value.
Additionally, all three methods must be able to calculate multiple SNRs, exposure times or sensitivities at once if an array of specific parameters is provided.
Besides the implementation of the detector, the factory method ``create()`` of the class ``SensorFactory`` must be modified.
In detail, a new if-branch must be added which assembles all necessary parameters for the constructor of the new component and calls the constructor.
Besides the implementation of the detector, the factory method ``create()`` of the class ``SensorFactory`` can be modified.

View File

@ -4,7 +4,7 @@ from ..IRadiant import IRadiant
from ...lib.logger import logger
from ..Entry import Entry
from astropy import units as u
from typing import Union, Callable
from typing import Union
import numpy as np
@ -21,10 +21,7 @@ class Filter(AHotOpticalComponent):
L=dict(cwl=3600 * u.nm, bw=1200 * u.nm), M=dict(cwl=4800 * u.nm, bw=800 * u.nm),
N=dict(cwl=10200 * u.nm, bw=2500 * u.nm))
@u.quantity_input(temp=[u.Kelvin, u.Celsius], obstructor_temp=[u.Kelvin, u.Celsius])
def __init__(self, parent: IRadiant, transmittance: Union[SpectralQty, Callable[[u.Quantity], u.Quantity]],
emissivity: Union[str, float] = 1, temp: u.Quantity = 0 * u.K, obstruction: float = 0,
obstructor_temp: u.Quantity = 0 * u.K, obstructor_emissivity: float = 1):
def __init__(self, **kwargs):
"""
Instantiate a new filter model
@ -32,8 +29,15 @@ class Filter(AHotOpticalComponent):
----------
parent : IRadiant
The parent element of the optical component from which the electromagnetic radiation is received.
transmittance : Union[SpectralQty, Callable]
The spectral transmittance coefficients of the filter.
transmittance : str
Path to the file containing the spectral transmittance-coefficients of the filter element.
The format of the file will be guessed by `astropy.io.ascii.read()`.
band : str
The spectral band of the filter. Can be one of [U, B, V, R, I, J, H, K].
start : length-quantity
Start wavelength of the pass-band
end : length-quantity
End wavelength of the pass-band
emissivity : Union[str, float]
The spectral emissivity coefficient for the optical surface.
temp: Quantity in Kelvin / Celsius
@ -48,14 +52,22 @@ class Filter(AHotOpticalComponent):
obstructor_emissivity : float
Emissivity of the obstructing component.
"""
super().__init__(parent, emissivity, temp, obstruction, obstructor_temp, obstructor_emissivity)
self._transmittance = transmittance
args = dict()
if "band" in kwargs:
args = self._fromBand(**kwargs)
elif "transmittance" in kwargs:
args = self._fromFile(**kwargs)
elif "start" in kwargs and "end" in kwargs:
args = self._fromRange(**kwargs)
else:
logger.error("Wrong parameters for filter.")
self._transmittance = args.pop("transmittance")
super().__init__(**args)
@classmethod
# @u.quantity_input(temp=[u.Kelvin, u.Celsius], obstructor_temp=[u.Kelvin, u.Celsius])
def fromBand(cls, parent: IRadiant, band: str, emissivity: Union[str, float] = 1, temp: u.Quantity = 0 * u.K,
obstruction: float = 0, obstructor_temp: u.Quantity = 0 * u.K,
obstructor_emissivity: float = 1) -> "Filter":
def _fromBand(self, parent: IRadiant, band: str, emissivity: Union[str, float] = 1, temp: u.Quantity = 0 * u.K,
obstruction: float = 0, obstructor_temp: u.Quantity = 0 * u.K,
obstructor_emissivity: float = 1) -> dict:
"""
Instantiate a new filter model from a spectral band. The filter will be modelled as bandpass filter of
infinite order and therefore similar to a hat-function.
@ -82,20 +94,19 @@ class Filter(AHotOpticalComponent):
Returns
-------
filter : Filter
The instantiated filter object.
args : dict
The arguments for the class instantiation.
"""
if band not in cls._band.keys():
logger.error("Band has to be one of '[" + ", ".join(list(cls._band.keys())) + "]'")
return cls.fromRange(parent, cls._band[band]["cwl"] - cls._band[band]["bw"] / 2,
cls._band[band]["cwl"] + cls._band[band]["bw"] / 2, emissivity, temp, obstruction,
obstructor_temp, obstructor_emissivity)
if band not in self._band.keys():
logger.error("Band has to be one of '[" + ", ".join(list(self._band.keys())) + "]'")
return self._fromRange(parent, self._band[band]["cwl"] - self._band[band]["bw"] / 2,
self._band[band]["cwl"] + self._band[band]["bw"] / 2, emissivity, temp, obstruction,
obstructor_temp, obstructor_emissivity)
@classmethod
# @u.quantity_input(temp=[u.Kelvin, u.Celsius], obstructor_temp=[u.Kelvin, u.Celsius])
def fromFile(cls, parent: IRadiant, transmittance: str, emissivity: Union[str, float] = 1,
temp: u.Quantity = 0 * u.K, obstruction: float = 0, obstructor_temp: u.Quantity = 0 * u.K,
obstructor_emissivity: float = 1) -> "Filter":
def _fromFile(self, parent: IRadiant, transmittance: str, emissivity: Union[str, float] = 1,
temp: u.Quantity = 0 * u.K, obstruction: float = 0, obstructor_temp: u.Quantity = 0 * u.K,
obstructor_emissivity: float = 1) -> dict:
"""
Instantiate a new filter model from a file containing the spectral transmittance coefficients.
@ -122,17 +133,17 @@ class Filter(AHotOpticalComponent):
Returns
-------
filter : Filter
The instantiated filter object.
args : dict
The arguments for the class instantiation.
"""
return cls(parent, SpectralQty.fromFile(transmittance, u.nm, u.dimensionless_unscaled), emissivity, temp,
obstruction, obstructor_temp, obstructor_emissivity)
return {"parent": parent, "transmittance": SpectralQty.fromFile(transmittance, u.nm, u.dimensionless_unscaled),
"emissivity": emissivity, "temp": temp, "obstruction": obstruction, "obstructor_temp": obstructor_temp,
"obstructor_emissivity": obstructor_emissivity}
@classmethod
# @u.quantity_input(start="length", end="length", temp=[u.Kelvin, u.Celsius], obstructor_temp=[u.Kelvin, u.Celsius])
def fromRange(cls, parent: IRadiant, start: u.Quantity, end: u.Quantity, emissivity: Union[str, float] = 1,
temp: u.Quantity = 0 * u.K, obstruction: float = 0, obstructor_temp: u.Quantity = 0 * u.K,
obstructor_emissivity: float = 1) -> "Filter":
def _fromRange(self, parent: IRadiant, start: u.Quantity, end: u.Quantity, emissivity: Union[str, float] = 1,
temp: u.Quantity = 0 * u.K, obstruction: float = 0, obstructor_temp: u.Quantity = 0 * u.K,
obstructor_emissivity: float = 1) -> dict:
"""
Instantiate a new filter model from a spectral range. The filter will be modelled as bandpass filter of
infinite order and therefore similar to a hat-function.
@ -161,11 +172,12 @@ class Filter(AHotOpticalComponent):
Returns
-------
filter : Filter
The instantiated filter object.
args : dict
The arguments for the class instantiation.
"""
return cls(parent, cls.__filter_factory(start, end), emissivity, temp,
obstruction, obstructor_temp, obstructor_emissivity)
return {"parent": parent, "transmittance": self.__filter_factory(start, end),
"emissivity": emissivity, "temp": temp, "obstruction": obstruction, "obstructor_temp": obstructor_temp,
"obstructor_emissivity": obstructor_emissivity}
def _propagate(self, sqty: SpectralQty) -> SpectralQty:
"""
@ -201,8 +213,8 @@ class Filter(AHotOpticalComponent):
lambda : Callable[[u.Quantity], u.Quantity]
The filter function
"""
return lambda wl: np.logical_and(np.greater_equal(wl, start), np.greater_equal(end, wl)).astype(int) *\
u.dimensionless_unscaled
return lambda wl: np.logical_and(np.greater_equal(wl, start), np.greater_equal(end, wl)).astype(
int) * u.dimensionless_unscaled
@staticmethod
def check_config(conf: Entry) -> Union[None, str]:

View File

@ -37,26 +37,16 @@ class OpticalComponentFactory(ARadiantFactory):
obj : AOpticalComponent
The created optical component
"""
opts = self.collectOptions(options)
if parent is not None:
# New component is of type Optical Component
opts = self.collectOptions(options)
opts["parent"] = parent
class_ = getattr(oc, options.type)
if options.type in ["Atmosphere", "StrayLight", "CosmicBackground", "Mirror", "Lens", "BeamSplitter"]:
if hasattr(oc, options.type):
class_ = getattr(oc, options.type)
return class_(**opts)
elif options.type == "Filter":
if hasattr(options, "band"):
return oc.Filter.fromBand(**opts)
elif hasattr(options, "transmittance"):
return oc.Filter.fromFile(**opts)
elif hasattr(options, "start") and hasattr(options, "end"):
return oc.Filter.fromRange(**opts)
else:
logger.error("Wrong parameters for filter.")
else:
logger.error("Unknown optical component type: '" + options.type + "'")
else:
logger.error("No parent given for optical component.")
logger.error("Parent object is required for optical component.")
def fromConfigBatch(self, conf: Entry, parent: IRadiant) -> AOpticalComponent:
"""

View File

@ -2,8 +2,7 @@ from ..AFactory import AFactory
from ..IRadiant import IRadiant
from ..Entry import Entry
from .ASensor import ASensor
from .Imager import Imager
from .Heterodyne import Heterodyne
from ...classes import sensor as sensor
from ...lib.logger import logger
@ -38,29 +37,13 @@ class SensorFactory(AFactory):
obj : ASensor
The created sensor object
"""
opts = self.collectOptions(options)
if options.type == "Imager":
if parent is not None:
opts = self.collectOptions(options)
args = dict(parent=parent, **opts, common_conf=self._common_conf)
if hasattr(options, "center_offset"):
# noinspection PyCallingNonCallable
args["center_offset"] = options.center_offset()
if hasattr(options, "photometric_aperture"):
if hasattr(options.photometric_aperture, "shape") and isinstance(
options.photometric_aperture.shape, Entry):
args["shape"] = options.photometric_aperture.shape()
if hasattr(options.photometric_aperture, "contained_energy") and isinstance(
options.photometric_aperture.contained_energy, Entry):
args["contained_energy"] = options.photometric_aperture.contained_energy()
if hasattr(options.photometric_aperture, "aperture_size") and isinstance(
options.photometric_aperture.aperture_size, Entry):
args["aperture_size"] = options.photometric_aperture.aperture_size()
return Imager(**args)
elif options.type == "Heterodyne":
args = dict(parent=parent, **opts, common_conf=self._common_conf)
if hasattr(options, "n_on"):
# noinspection PyCallingNonCallable
args["n_on"] = options.n_on()
return Heterodyne(**args)
if hasattr(sensor, options.type):
class_ = getattr(sensor, options.type)
return class_(**args)
else:
logger.error("Unknown sensor type: '" + options.type + "'")
else:
logger.error("Wrong sensor type: " + options.type)
logger.error("Parent object is required for sensor.")

View File

@ -1,4 +1,3 @@
import astropy.units as u
from ..ARadiantFactory import ARadiantFactory
from ..Entry import Entry
from ..IRadiant import IRadiant
@ -38,18 +37,13 @@ class TargetFactory(ARadiantFactory):
obj : ATarget
The created target object
"""
opts = self.collectOptions(options)
if parent is None:
# New component is of type target
opts = self.collectOptions(options)
opts["wl_bins"] = self._common_conf.wl_bins.val
if options.type == "BlackBodyTarget":
# Black Body Target
if "mag" in opts and type(opts["mag"]) == str:
opts["mag"] = float(opts["mag"]) * u.mag
return tg.BlackBodyTarget(**opts)
elif options.type == "FileTarget":
# File Target
return getattr(tg, options.type)(**opts)
if hasattr(tg, options.type):
class_ = getattr(tg, options.type)
return class_(**opts)
else:
logger.error("Unknown target type: '" + options.type + "'")
else:

View File

@ -8,14 +8,14 @@ class TestFilter(TestCase):
def test_fromBand(self):
wl = np.array([400, 500, 501, 545, 589, 590, 600]) << u.nm
target = BlackBodyTarget(wl, temp=5778 * u.K, mag=10 * u.mag, band="U")
filt = Filter.fromBand(target, "V")
filt = Filter(parent=target, band="V")
self.assertEqual(filt.calcSignal()[0], SpectralQty(wl, np.array([0.0, 0.0, 0.0, 5.52730709e-15, 5.29671115e-15,
5.29030718e-15, 0.0]) << u.W /
(u.m ** 2 * u.nm)))
5.29030718e-15, 0.0]) << u.W /
(u.m ** 2 * u.nm)))
def test_fromFile(self):
target = FileTarget("tests/data/target/target_demo_1.csv", np.arange(200, 210, 1) << u.nm)
filt = Filter.fromFile(target, "tests/data/filter/filter_transmittance.csv")
filt = Filter(parent=target, transmittance="tests/data/filter/filter_transmittance.csv")
self.assertEqual(filt.calcSignal()[0],
SpectralQty(np.arange(200, 210, 1) << u.nm, np.array([1.10e-15, 1.20e-15, 1.30e-15, 1.40e-15,
1.35e-15, 1.44e-15, 1.53e-15, 1.44e-15,

View File

@ -25,7 +25,8 @@ class TestRadiantFactory(TestCase):
"tests/data/mirror/emissivity.csv", 70 * u.K)
parent_2 = oc.Mirror(parent_2, "tests/data/mirror/reflectance.csv",
"tests/data/mirror/emissivity.csv", 70 * u.K)
parent_2 = oc.Filter.fromRange(parent_2, 400 * u.nm, 480 * u.nm, "tests/data/filter/emissivity.csv", 70 * u.K)
parent_2 = oc.Filter(parent=parent_2, start=400 * u.nm, end=480 * u.nm,
emissivity="tests/data/filter/emissivity.csv", temp=70 * u.K)
parent_2 = oc.Lens(parent_2, "tests/data/lens/transmittance.csv", "tests/data/lens/emissivity.csv", 70 * u.K)
self.assertEqual(parent.calcSignal()[0], parent_2.calcSignal()[0])