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. 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. 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 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. 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. 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. Besides the implementation of the detector, the factory method ``create()`` of the class ``SensorFactory`` can 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.

View File

@ -4,7 +4,7 @@ from ..IRadiant import IRadiant
from ...lib.logger import logger from ...lib.logger import logger
from ..Entry import Entry from ..Entry import Entry
from astropy import units as u from astropy import units as u
from typing import Union, Callable from typing import Union
import numpy as np 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), 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)) 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, **kwargs):
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):
""" """
Instantiate a new filter model Instantiate a new filter model
@ -32,8 +29,15 @@ class Filter(AHotOpticalComponent):
---------- ----------
parent : IRadiant parent : IRadiant
The parent element of the optical component from which the electromagnetic radiation is received. The parent element of the optical component from which the electromagnetic radiation is received.
transmittance : Union[SpectralQty, Callable] transmittance : str
The spectral transmittance coefficients of the filter. 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] emissivity : Union[str, float]
The spectral emissivity coefficient for the optical surface. The spectral emissivity coefficient for the optical surface.
temp: Quantity in Kelvin / Celsius temp: Quantity in Kelvin / Celsius
@ -48,14 +52,22 @@ class Filter(AHotOpticalComponent):
obstructor_emissivity : float obstructor_emissivity : float
Emissivity of the obstructing component. Emissivity of the obstructing component.
""" """
super().__init__(parent, emissivity, temp, obstruction, obstructor_temp, obstructor_emissivity) args = dict()
self._transmittance = transmittance 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]) # @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, 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, obstruction: float = 0, obstructor_temp: u.Quantity = 0 * u.K,
obstructor_emissivity: float = 1) -> "Filter": obstructor_emissivity: float = 1) -> dict:
""" """
Instantiate a new filter model from a spectral band. The filter will be modelled as bandpass filter of 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. infinite order and therefore similar to a hat-function.
@ -82,20 +94,19 @@ class Filter(AHotOpticalComponent):
Returns Returns
------- -------
filter : Filter args : dict
The instantiated filter object. The arguments for the class instantiation.
""" """
if band not in cls._band.keys(): if band not in self._band.keys():
logger.error("Band has to be one of '[" + ", ".join(list(cls._band.keys())) + "]'") logger.error("Band has to be one of '[" + ", ".join(list(self._band.keys())) + "]'")
return cls.fromRange(parent, cls._band[band]["cwl"] - cls._band[band]["bw"] / 2, return self._fromRange(parent, self._band[band]["cwl"] - self._band[band]["bw"] / 2,
cls._band[band]["cwl"] + cls._band[band]["bw"] / 2, emissivity, temp, obstruction, self._band[band]["cwl"] + self._band[band]["bw"] / 2, emissivity, temp, obstruction,
obstructor_temp, obstructor_emissivity) obstructor_temp, obstructor_emissivity)
@classmethod
# @u.quantity_input(temp=[u.Kelvin, u.Celsius], obstructor_temp=[u.Kelvin, u.Celsius]) # @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, 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, temp: u.Quantity = 0 * u.K, obstruction: float = 0, obstructor_temp: u.Quantity = 0 * u.K,
obstructor_emissivity: float = 1) -> "Filter": obstructor_emissivity: float = 1) -> dict:
""" """
Instantiate a new filter model from a file containing the spectral transmittance coefficients. Instantiate a new filter model from a file containing the spectral transmittance coefficients.
@ -122,17 +133,17 @@ class Filter(AHotOpticalComponent):
Returns Returns
------- -------
filter : Filter args : dict
The instantiated filter object. The arguments for the class instantiation.
""" """
return cls(parent, SpectralQty.fromFile(transmittance, u.nm, u.dimensionless_unscaled), emissivity, temp, return {"parent": parent, "transmittance": SpectralQty.fromFile(transmittance, u.nm, u.dimensionless_unscaled),
obstruction, obstructor_temp, obstructor_emissivity) "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]) # @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, 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, temp: u.Quantity = 0 * u.K, obstruction: float = 0, obstructor_temp: u.Quantity = 0 * u.K,
obstructor_emissivity: float = 1) -> "Filter": obstructor_emissivity: float = 1) -> dict:
""" """
Instantiate a new filter model from a spectral range. The filter will be modelled as bandpass filter of 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. infinite order and therefore similar to a hat-function.
@ -161,11 +172,12 @@ class Filter(AHotOpticalComponent):
Returns Returns
------- -------
filter : Filter args : dict
The instantiated filter object. The arguments for the class instantiation.
""" """
return cls(parent, cls.__filter_factory(start, end), emissivity, temp, return {"parent": parent, "transmittance": self.__filter_factory(start, end),
obstruction, obstructor_temp, obstructor_emissivity) "emissivity": emissivity, "temp": temp, "obstruction": obstruction, "obstructor_temp": obstructor_temp,
"obstructor_emissivity": obstructor_emissivity}
def _propagate(self, sqty: SpectralQty) -> SpectralQty: def _propagate(self, sqty: SpectralQty) -> SpectralQty:
""" """
@ -201,8 +213,8 @@ class Filter(AHotOpticalComponent):
lambda : Callable[[u.Quantity], u.Quantity] lambda : Callable[[u.Quantity], u.Quantity]
The filter function The filter function
""" """
return lambda wl: np.logical_and(np.greater_equal(wl, start), np.greater_equal(end, wl)).astype(int) *\ return lambda wl: np.logical_and(np.greater_equal(wl, start), np.greater_equal(end, wl)).astype(
u.dimensionless_unscaled int) * u.dimensionless_unscaled
@staticmethod @staticmethod
def check_config(conf: Entry) -> Union[None, str]: def check_config(conf: Entry) -> Union[None, str]:

View File

@ -37,26 +37,16 @@ class OpticalComponentFactory(ARadiantFactory):
obj : AOpticalComponent obj : AOpticalComponent
The created optical component The created optical component
""" """
opts = self.collectOptions(options)
if parent is not None: if parent is not None:
# New component is of type Optical Component opts = self.collectOptions(options)
opts["parent"] = parent opts["parent"] = parent
class_ = getattr(oc, options.type) if hasattr(oc, options.type):
if options.type in ["Atmosphere", "StrayLight", "CosmicBackground", "Mirror", "Lens", "BeamSplitter"]: class_ = getattr(oc, options.type)
return class_(**opts) 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: else:
logger.error("Unknown optical component type: '" + options.type + "'") logger.error("Unknown optical component type: '" + options.type + "'")
else: 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: def fromConfigBatch(self, conf: Entry, parent: IRadiant) -> AOpticalComponent:
""" """

View File

@ -2,8 +2,7 @@ from ..AFactory import AFactory
from ..IRadiant import IRadiant from ..IRadiant import IRadiant
from ..Entry import Entry from ..Entry import Entry
from .ASensor import ASensor from .ASensor import ASensor
from .Imager import Imager from ...classes import sensor as sensor
from .Heterodyne import Heterodyne
from ...lib.logger import logger from ...lib.logger import logger
@ -38,29 +37,13 @@ class SensorFactory(AFactory):
obj : ASensor obj : ASensor
The created sensor object The created sensor object
""" """
opts = self.collectOptions(options) if parent is not None:
opts = self.collectOptions(options)
if options.type == "Imager":
args = dict(parent=parent, **opts, common_conf=self._common_conf) args = dict(parent=parent, **opts, common_conf=self._common_conf)
if hasattr(options, "center_offset"): if hasattr(sensor, options.type):
# noinspection PyCallingNonCallable class_ = getattr(sensor, options.type)
args["center_offset"] = options.center_offset() return class_(**args)
if hasattr(options, "photometric_aperture"): else:
if hasattr(options.photometric_aperture, "shape") and isinstance( logger.error("Unknown sensor type: '" + options.type + "'")
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)
else: 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 ..ARadiantFactory import ARadiantFactory
from ..Entry import Entry from ..Entry import Entry
from ..IRadiant import IRadiant from ..IRadiant import IRadiant
@ -38,18 +37,13 @@ class TargetFactory(ARadiantFactory):
obj : ATarget obj : ATarget
The created target object The created target object
""" """
opts = self.collectOptions(options)
if parent is None: if parent is None:
# New component is of type target opts = self.collectOptions(options)
opts["wl_bins"] = self._common_conf.wl_bins.val opts["wl_bins"] = self._common_conf.wl_bins.val
if options.type == "BlackBodyTarget": if hasattr(tg, options.type):
# Black Body Target class_ = getattr(tg, options.type)
if "mag" in opts and type(opts["mag"]) == str: return class_(**opts)
opts["mag"] = float(opts["mag"]) * u.mag
return tg.BlackBodyTarget(**opts)
elif options.type == "FileTarget":
# File Target
return getattr(tg, options.type)(**opts)
else: else:
logger.error("Unknown target type: '" + options.type + "'") logger.error("Unknown target type: '" + options.type + "'")
else: else:

View File

@ -8,14 +8,14 @@ class TestFilter(TestCase):
def test_fromBand(self): def test_fromBand(self):
wl = np.array([400, 500, 501, 545, 589, 590, 600]) << u.nm 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") 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, 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 / 5.29030718e-15, 0.0]) << u.W /
(u.m ** 2 * u.nm))) (u.m ** 2 * u.nm)))
def test_fromFile(self): def test_fromFile(self):
target = FileTarget("tests/data/target/target_demo_1.csv", np.arange(200, 210, 1) << u.nm) 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], 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, 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, 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) "tests/data/mirror/emissivity.csv", 70 * u.K)
parent_2 = oc.Mirror(parent_2, "tests/data/mirror/reflectance.csv", parent_2 = oc.Mirror(parent_2, "tests/data/mirror/reflectance.csv",
"tests/data/mirror/emissivity.csv", 70 * u.K) "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) 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]) self.assertEqual(parent.calcSignal()[0], parent_2.calcSignal()[0])