Factories combined via AFactory
This commit is contained in:
parent
9d49188e4a
commit
407d78c7c8
76
esbo_etc/classes/AFactory.py
Normal file
76
esbo_etc/classes/AFactory.py
Normal file
@ -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.")
|
@ -1,30 +1,27 @@
|
|||||||
import astropy.units as u
|
from .AFactory import AFactory
|
||||||
from .Entry import Entry
|
from .Entry import Entry
|
||||||
from .IRadiant import IRadiant
|
from .IRadiant import IRadiant
|
||||||
from ..classes import optical_component as oc
|
from abc import abstractmethod
|
||||||
from ..classes import target as tg
|
|
||||||
from ..lib.logger import logger
|
|
||||||
import copy
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
class RadiantFactory:
|
class RadiantFactory(AFactory):
|
||||||
"""
|
"""
|
||||||
A Factory creating objects of the type IRadiant
|
A Factory creating objects of the type IRadiant
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@u.quantity_input(wl_bins="length")
|
@abstractmethod
|
||||||
def __init__(self, wl_bins: u.Quantity):
|
def __init__(self, common_conf: Entry):
|
||||||
"""
|
"""
|
||||||
Instantiate a new factory object
|
Instantiate a new factory object
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
wl_bins : Quantity
|
common_conf : Entry
|
||||||
Wavelengths used for binning
|
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:
|
def create(self, options: Entry, parent: IRadiant = None) -> IRadiant:
|
||||||
"""
|
"""
|
||||||
Create a new object of the type IRadiant
|
Create a new object of the type IRadiant
|
||||||
@ -40,74 +37,4 @@ class RadiantFactory:
|
|||||||
obj : IRadiant
|
obj : IRadiant
|
||||||
The created object
|
The created object
|
||||||
"""
|
"""
|
||||||
if hasattr(options, "type"):
|
pass
|
||||||
# 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
|
|
||||||
|
@ -47,10 +47,14 @@ class esbo_etc:
|
|||||||
|
|
||||||
# Set up components
|
# Set up components
|
||||||
logger.info("Setting up components...", extra={"spinning": True})
|
logger.info("Setting up components...", extra={"spinning": True})
|
||||||
oc_factory = eetc.classes.RadiantFactory(self.conf.common.wl_bins())
|
target_factory = eetc.TargetFactory(self.conf.common)
|
||||||
parent = oc_factory.fromConfigBatch(self.conf)
|
oc_factory = eetc.OpticalComponentFactory(self.conf.common)
|
||||||
sensor_factory = eetc.SensorFactory(parent, self.conf.common)
|
sensor_factory = eetc.SensorFactory(self.conf.common)
|
||||||
detector = sensor_factory.create(self.conf.instrument.sensor)
|
|
||||||
|
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
|
# Calculate results
|
||||||
res = None
|
res = None
|
||||||
|
@ -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
|
@ -7,3 +7,4 @@ from .Lens import *
|
|||||||
from .BeamSplitter import *
|
from .BeamSplitter import *
|
||||||
from .Mirror import *
|
from .Mirror import *
|
||||||
from .CosmicBackground import *
|
from .CosmicBackground import *
|
||||||
|
from .OpticalComponentFactory import *
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
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
|
||||||
@ -6,36 +7,41 @@ from .Heterodyne import Heterodyne
|
|||||||
from ...lib.logger import logger
|
from ...lib.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class SensorFactory:
|
class SensorFactory(AFactory):
|
||||||
"""
|
"""
|
||||||
A Factory creating objects of the type ASensor
|
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
|
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
|
Parameters
|
||||||
----------
|
----------
|
||||||
options : Entry
|
options : Entry
|
||||||
The options to be used as parameters for the instantiation of the new object.
|
The options to be used as parameters for the instantiation of the new object.
|
||||||
|
parent : IRadiant
|
||||||
|
The parent element of the object.
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
obj : ASensor
|
obj : ASensor
|
||||||
The created sensor object
|
The created sensor object
|
||||||
"""
|
"""
|
||||||
|
opts = self.collectOptions(options)
|
||||||
|
|
||||||
if options.type == "Imager":
|
if options.type == "Imager":
|
||||||
args = dict(parent=self.__parent, quantum_efficiency=options.pixel.quantum_efficiency(),
|
args = dict(parent=parent, **opts, common_conf=self._common_conf)
|
||||||
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)
|
|
||||||
if hasattr(options, "center_offset"):
|
if hasattr(options, "center_offset"):
|
||||||
# noinspection PyCallingNonCallable
|
# noinspection PyCallingNonCallable
|
||||||
args["center_offset"] = options.center_offset()
|
args["center_offset"] = options.center_offset()
|
||||||
@ -51,10 +57,7 @@ class SensorFactory:
|
|||||||
args["aperture_size"] = options.photometric_aperture.aperture_size()
|
args["aperture_size"] = options.photometric_aperture.aperture_size()
|
||||||
return Imager(**args)
|
return Imager(**args)
|
||||||
elif options.type == "Heterodyne":
|
elif options.type == "Heterodyne":
|
||||||
args = dict(parent=self.__parent, aperture_efficiency=options.aperture_efficiency(),
|
args = dict(parent=parent, **opts, common_conf=self._common_conf)
|
||||||
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)
|
|
||||||
if hasattr(options, "n_on"):
|
if hasattr(options, "n_on"):
|
||||||
# noinspection PyCallingNonCallable
|
# noinspection PyCallingNonCallable
|
||||||
args["n_on"] = options.n_on()
|
args["n_on"] = options.n_on()
|
||||||
|
55
esbo_etc/classes/target/TargetFactory.py
Normal file
55
esbo_etc/classes/target/TargetFactory.py
Normal file
@ -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.")
|
@ -1,3 +1,4 @@
|
|||||||
from .ATarget import *
|
from .ATarget import *
|
||||||
from .BlackBodyTarget import *
|
from .BlackBodyTarget import *
|
||||||
from .FileTarget import *
|
from .FileTarget import *
|
||||||
|
from .TargetFactory import *
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from esbo_etc.classes.Config import Configuration
|
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
|
import esbo_etc.classes.optical_component as oc
|
||||||
from esbo_etc.classes.target import BlackBodyTarget
|
from esbo_etc.classes.target import BlackBodyTarget
|
||||||
import astropy.units as u
|
import astropy.units as u
|
||||||
@ -9,8 +10,10 @@ import astropy.units as u
|
|||||||
class TestRadiantFactory(TestCase):
|
class TestRadiantFactory(TestCase):
|
||||||
def test_fromConfigBatch(self):
|
def test_fromConfigBatch(self):
|
||||||
conf = Configuration("tests/data/esbo-etc_defaults.xml").conf
|
conf = Configuration("tests/data/esbo-etc_defaults.xml").conf
|
||||||
factory = RadiantFactory(conf.common.wl_bins())
|
target_factory = TargetFactory(conf.common)
|
||||||
parent = factory.fromConfigBatch(conf)
|
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 = BlackBodyTarget(conf.common.wl_bins(), 5778 * u.K, 10 * u.mag, "V")
|
||||||
parent_2 = oc.Atmosphere(parent_2, "tests/data/atmosphere/transmittance.csv",
|
parent_2 = oc.Atmosphere(parent_2, "tests/data/atmosphere/transmittance.csv",
|
||||||
|
Loading…
Reference in New Issue
Block a user