diff --git a/esbo_etc/classes/AFactory.py b/esbo_etc/classes/AFactory.py new file mode 100644 index 0000000..62d5dc1 --- /dev/null +++ b/esbo_etc/classes/AFactory.py @@ -0,0 +1,76 @@ +from abc import abstractmethod +from .Entry import Entry +from .IRadiant import IRadiant +from ..lib.logger import logger +import copy +import re + + +class AFactory: + """ + A Factory for creating objects of the radiation transportation pipeline + """ + + @abstractmethod + def __init__(self, common_conf: Entry): + """ + Instantiate a new factory object + + Parameters + ---------- + common_conf : Entry + The common configuration of the configuration file + """ + self._common_conf = common_conf + + @abstractmethod + def create(self, options: Entry, parent: IRadiant = None): + """ + Create a new object of the radiation transportation pipeline + + Parameters + ---------- + options : Entry + The options to be used as parameters for the instantiation of the new object. + parent : IRadiant + The optional parent element of the object (necessary for subclasses of AOpticalComponent and ASensor). + Returns + ------- + obj + The created object + """ + pass + + def collectOptions(self, options: Entry) -> dict: + """ + Collect all options from the configuration file as dictionary + + Parameters + ---------- + options : Entry + The options to be used as parameters for the instantiation of the new object. + + Returns + ------- + opts : dict + The collected options as dictionary + """ + # if hasattr(options, "type"): + # Copy custom attributes of the Entry to a dictionary + opts = copy.copy(vars(options)) + + for i in vars(options): + obj = getattr(options, i) + if isinstance(obj, Entry): + opts.pop(i, None) + additional_opts = self.collectOptions(obj) + if len(additional_opts) == 1 and list(additional_opts.keys())[0] == "val": + additional_opts[i] = additional_opts.pop("val") + opts.update(additional_opts) + + # Remove unnecessary keys + for attrib in list(filter(re.compile(".*_unit$").match, opts)) + ["comment", "type"]: + opts.pop(attrib, None) + return opts + # else: + # logger.error("Component needs to have a type specified.") diff --git a/esbo_etc/classes/RadiantFactory.py b/esbo_etc/classes/RadiantFactory.py index 67f138a..a29376a 100644 --- a/esbo_etc/classes/RadiantFactory.py +++ b/esbo_etc/classes/RadiantFactory.py @@ -1,30 +1,27 @@ -import astropy.units as u +from .AFactory import AFactory from .Entry import Entry from .IRadiant import IRadiant -from ..classes import optical_component as oc -from ..classes import target as tg -from ..lib.logger import logger -import copy -import re +from abc import abstractmethod -class RadiantFactory: +class RadiantFactory(AFactory): """ A Factory creating objects of the type IRadiant """ - @u.quantity_input(wl_bins="length") - def __init__(self, wl_bins: u.Quantity): + @abstractmethod + def __init__(self, common_conf: Entry): """ Instantiate a new factory object Parameters ---------- - wl_bins : Quantity - Wavelengths used for binning + common_conf : Entry + The common configuration of the configuration file """ - self.__wl_bins = wl_bins + super().__init__(common_conf) + @abstractmethod def create(self, options: Entry, parent: IRadiant = None) -> IRadiant: """ Create a new object of the type IRadiant @@ -40,74 +37,4 @@ class RadiantFactory: obj : IRadiant The created object """ - if hasattr(options, "type"): - # Copy custom attributes of the Entry to a dictionary - attribs = copy.copy(vars(options)) - # Remove unnecessary keys - for attrib in list(filter(re.compile(".*_unit$").match, attribs)) + ["comment", "type"]: - attribs.pop(attrib, None) - if parent is None: - # New component is of type target - attribs["wl_bins"] = self.__wl_bins - if options.type == "BlackBodyTarget": - # Black Body Target - if "mag" in attribs and type(attribs["mag"]) == str: - attribs["mag"] = float(attribs["mag"]) * u.mag - return tg.BlackBodyTarget(**attribs) - elif options.type == "FileTarget": - # File Target - return getattr(tg, options.type)(**attribs) - else: - logger.error("Unknown target type: '" + options.type + "'") - else: - # New component is of type Optical Component - attribs["parent"] = parent - if "obstruction" in attribs: - attribs["obstruction"] = float(attribs["obstruction"]) - class_ = getattr(oc, options.type) - if options.type in ["Atmosphere", "StrayLight", "CosmicBackground", "Mirror", "Lens", "BeamSplitter"]: - return class_(**attribs) - elif options.type == "Filter": - if hasattr(options, "band"): - return oc.Filter.fromBand(**attribs) - elif hasattr(options, "transmittance"): - return oc.Filter.fromFile(**attribs) - elif hasattr(options, "start") and hasattr(options, "end"): - return oc.Filter.fromRange(**attribs) - else: - logger.error("Wrong parameters for filter.") - else: - logger.error("Unknown optical component type: '" + options.type + "'") - else: - logger.error("Optical component needs to have a type specified.") - - def fromConfigBatch(self, conf: Entry) -> IRadiant: - """ - Initialize a decorated target from a configuration. - - Parameters - ---------- - conf : Entry - The configuration defining the target and the decorators. - - Returns - ------- - parent : IRadiant - The decorated target. - """ - parent = self.create(conf.astroscene.target) - if hasattr(conf.astroscene, "optical_component"): - for entry in conf.astroscene.optical_component if type(conf.astroscene.optical_component) == list else\ - [conf.astroscene.optical_component]: - parent = self.create(entry, parent) - if hasattr(conf, "common_optics") and hasattr(conf.common_optics, "optical_component"): - for entry in conf.common_optics.optical_component if type(conf.common_optics.optical_component) == \ - list else [conf.common_optics.optical_component]: - if isinstance(entry, Entry): - parent = self.create(entry, parent) - if hasattr(conf, "instrument") and hasattr(conf.instrument, "optical_component"): - for entry in conf.instrument.optical_component if type(conf.instrument.optical_component) == list else\ - [conf.instrument.optical_component]: - if isinstance(entry, Entry): - parent = self.create(entry, parent) - return parent + pass diff --git a/esbo_etc/classes/esbo_etc.py b/esbo_etc/classes/esbo_etc.py index 926bc31..296488f 100644 --- a/esbo_etc/classes/esbo_etc.py +++ b/esbo_etc/classes/esbo_etc.py @@ -47,10 +47,14 @@ class esbo_etc: # Set up components logger.info("Setting up components...", extra={"spinning": True}) - oc_factory = eetc.classes.RadiantFactory(self.conf.common.wl_bins()) - parent = oc_factory.fromConfigBatch(self.conf) - sensor_factory = eetc.SensorFactory(parent, self.conf.common) - detector = sensor_factory.create(self.conf.instrument.sensor) + target_factory = eetc.TargetFactory(self.conf.common) + oc_factory = eetc.OpticalComponentFactory(self.conf.common) + sensor_factory = eetc.SensorFactory(self.conf.common) + + parent = target_factory.create(self.conf.astroscene.target) + parent = oc_factory.fromConfigBatch(self.conf, parent) + + detector = sensor_factory.create(self.conf.instrument.sensor, parent) # Calculate results res = None diff --git a/esbo_etc/classes/optical_component/OpticalComponentFactory.py b/esbo_etc/classes/optical_component/OpticalComponentFactory.py new file mode 100644 index 0000000..3bcab82 --- /dev/null +++ b/esbo_etc/classes/optical_component/OpticalComponentFactory.py @@ -0,0 +1,90 @@ +from ..RadiantFactory import RadiantFactory +from ..Entry import Entry +from ..IRadiant import IRadiant +from ...classes import optical_component as oc +from ...lib.logger import logger + + +class OpticalComponentFactory(RadiantFactory): + """ + A Factory creating objects of the type IRadiant + """ + + def __init__(self, common_conf: Entry): + """ + Instantiate a new factory object + + Parameters + ---------- + common_conf : Entry + The common configuration of the configuration file + """ + super().__init__(common_conf) + + def create(self, options: Entry, parent: IRadiant = None) -> IRadiant: + """ + Create a new object of the type IRadiant + + Parameters + ---------- + options : Entry + The options to be used as parameters for the instantiation of the new object. + parent : IRadiant + The optional parent element of the object (necessary for subclasses of AOpticalComponent). + Returns + ------- + obj : IRadiant + The created object + """ + opts = self.collectOptions(options) + if parent is not None: + # New component is of type Optical Component + opts["parent"] = parent + class_ = getattr(oc, options.type) + if options.type in ["Atmosphere", "StrayLight", "CosmicBackground", "Mirror", "Lens", "BeamSplitter"]: + 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.") + + def fromConfigBatch(self, conf: Entry, parent: IRadiant) -> IRadiant: + """ + Initialize a decorated target from a configuration. + + Parameters + ---------- + conf : Entry + The configuration defining the target and the decorators. + parent : IRadiant + The optional parent element of the object + + Returns + ------- + parent : IRadiant + The decorated parent object. + """ + if hasattr(conf.astroscene, "optical_component"): + for entry in conf.astroscene.optical_component if type(conf.astroscene.optical_component) == list else\ + [conf.astroscene.optical_component]: + parent = self.create(entry, parent) + if hasattr(conf, "common_optics") and hasattr(conf.common_optics, "optical_component"): + for entry in conf.common_optics.optical_component if type(conf.common_optics.optical_component) == \ + list else [conf.common_optics.optical_component]: + if isinstance(entry, Entry): + parent = self.create(entry, parent) + if hasattr(conf, "instrument") and hasattr(conf.instrument, "optical_component"): + for entry in conf.instrument.optical_component if type(conf.instrument.optical_component) == list else\ + [conf.instrument.optical_component]: + if isinstance(entry, Entry): + parent = self.create(entry, parent) + return parent diff --git a/esbo_etc/classes/optical_component/__init__.py b/esbo_etc/classes/optical_component/__init__.py index 61e5b01..7a72d2f 100644 --- a/esbo_etc/classes/optical_component/__init__.py +++ b/esbo_etc/classes/optical_component/__init__.py @@ -7,3 +7,4 @@ from .Lens import * from .BeamSplitter import * from .Mirror import * from .CosmicBackground import * +from .OpticalComponentFactory import * diff --git a/esbo_etc/classes/sensor/SensorFactory.py b/esbo_etc/classes/sensor/SensorFactory.py index e23ad1c..9d25a5e 100644 --- a/esbo_etc/classes/sensor/SensorFactory.py +++ b/esbo_etc/classes/sensor/SensorFactory.py @@ -1,3 +1,4 @@ +from ..AFactory import AFactory from ..IRadiant import IRadiant from ..Entry import Entry from .ASensor import ASensor @@ -6,36 +7,41 @@ from .Heterodyne import Heterodyne from ...lib.logger import logger -class SensorFactory: +class SensorFactory(AFactory): """ A Factory creating objects of the type ASensor """ - def __init__(self, parent: IRadiant, common_conf: Entry): + + def __init__(self, common_conf: Entry): """ Instantiate a new factory object - """ - self.__common_conf = common_conf - self.__parent = parent - def create(self, options: Entry) -> ASensor: + Parameters + ---------- + common_conf : Entry + The common configuration of the configuration file """ - Create a new object of the type ASensor + super().__init__(common_conf) + + def create(self, options: Entry, parent: IRadiant = None): + """ + Create a new sensor object Parameters ---------- options : Entry The options to be used as parameters for the instantiation of the new object. + parent : IRadiant + The parent element of the object. Returns ------- obj : ASensor The created sensor object """ + opts = self.collectOptions(options) + if options.type == "Imager": - args = dict(parent=self.__parent, quantum_efficiency=options.pixel.quantum_efficiency(), - pixel_geometry=options.pixel_geometry(), pixel_size=options.pixel.pixel_size(), - read_noise=options.pixel.sigma_read_out(), dark_current=options.pixel.dark_current(), - well_capacity=options.pixel.well_capacity(), f_number=options.f_number(), - common_conf=self.__common_conf) + args = dict(parent=parent, **opts, common_conf=self._common_conf) if hasattr(options, "center_offset"): # noinspection PyCallingNonCallable args["center_offset"] = options.center_offset() @@ -51,10 +57,7 @@ class SensorFactory: args["aperture_size"] = options.photometric_aperture.aperture_size() return Imager(**args) elif options.type == "Heterodyne": - args = dict(parent=self.__parent, aperture_efficiency=options.aperture_efficiency(), - main_beam_efficiency=options.main_beam_efficiency(), receiver_temp=options.receiver_temp(), - eta_fss=options.eta_fss(), lambda_line=options.lambda_line(), kappa=options.kappa(), - common_conf=self.__common_conf) + args = dict(parent=parent, **opts, common_conf=self._common_conf) if hasattr(options, "n_on"): # noinspection PyCallingNonCallable args["n_on"] = options.n_on() diff --git a/esbo_etc/classes/target/TargetFactory.py b/esbo_etc/classes/target/TargetFactory.py new file mode 100644 index 0000000..19e8b56 --- /dev/null +++ b/esbo_etc/classes/target/TargetFactory.py @@ -0,0 +1,55 @@ +import astropy.units as u +from ..RadiantFactory import RadiantFactory +from ..Entry import Entry +from ..IRadiant import IRadiant +from ...classes import target as tg +from ...lib.logger import logger + + +class TargetFactory(RadiantFactory): + """ + A Factory creating objects of the type IRadiant + """ + + def __init__(self, common_conf: Entry): + """ + Instantiate a new factory object + + Parameters + ---------- + common_conf : Entry + The common configuration of the configuration file + """ + super().__init__(common_conf) + + def create(self, options: Entry, parent: IRadiant = None) -> IRadiant: + """ + Create a new object of the type IRadiant + + Parameters + ---------- + options : Entry + The options to be used as parameters for the instantiation of the new object. + parent : IRadiant + The optional parent element of the object (necessary for subclasses of AOpticalComponent). + Returns + ------- + obj : IRadiant + The created object + """ + opts = self.collectOptions(options) + if parent is None: + # New component is of type target + 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) + else: + logger.error("Unknown target type: '" + options.type + "'") + else: + logger.error("No parent object allowed for target.") diff --git a/esbo_etc/classes/target/__init__.py b/esbo_etc/classes/target/__init__.py index 863660f..225c04b 100644 --- a/esbo_etc/classes/target/__init__.py +++ b/esbo_etc/classes/target/__init__.py @@ -1,3 +1,4 @@ from .ATarget import * from .BlackBodyTarget import * from .FileTarget import * +from .TargetFactory import * diff --git a/tests/test_RadiantFactory.py b/tests/test_RadiantFactory.py index bed831e..2634fdb 100644 --- a/tests/test_RadiantFactory.py +++ b/tests/test_RadiantFactory.py @@ -1,6 +1,7 @@ from unittest import TestCase from esbo_etc.classes.Config import Configuration -from esbo_etc.classes.RadiantFactory import RadiantFactory +from esbo_etc.classes.optical_component.OpticalComponentFactory import OpticalComponentFactory +from esbo_etc.classes.target.TargetFactory import TargetFactory import esbo_etc.classes.optical_component as oc from esbo_etc.classes.target import BlackBodyTarget import astropy.units as u @@ -9,8 +10,10 @@ import astropy.units as u class TestRadiantFactory(TestCase): def test_fromConfigBatch(self): conf = Configuration("tests/data/esbo-etc_defaults.xml").conf - factory = RadiantFactory(conf.common.wl_bins()) - parent = factory.fromConfigBatch(conf) + target_factory = TargetFactory(conf.common) + oc_factory = OpticalComponentFactory(conf.common) + parent = target_factory.create(conf.astroscene.target) + parent = oc_factory.fromConfigBatch(conf, parent) parent_2 = BlackBodyTarget(conf.common.wl_bins(), 5778 * u.K, 10 * u.mag, "V") parent_2 = oc.Atmosphere(parent_2, "tests/data/atmosphere/transmittance.csv",