Entry moved to own file,
check config added
This commit is contained in:
parent
0b3134ec03
commit
e342a91d1a
194
esbo_etc/classes/Config.py
Normal file
194
esbo_etc/classes/Config.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import xml.etree.ElementTree as eT
|
||||||
|
import numpy as np
|
||||||
|
import astropy.units as u
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from ..lib.helpers import error
|
||||||
|
from .Entry import Entry
|
||||||
|
from ..classes import target as tg
|
||||||
|
from ..classes import optical_component as oc
|
||||||
|
from ..classes import sensor as sensor
|
||||||
|
import difflib
|
||||||
|
import os.path
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
|
class Configuration(object):
|
||||||
|
"""
|
||||||
|
A Class to parse the XML configuration file.
|
||||||
|
Adapted from ExoSim (https://github.com/ExoSim/ExoSimPublic)
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
conf : Entry
|
||||||
|
Parsed configuration file as Entry-tree
|
||||||
|
"""
|
||||||
|
conf = None
|
||||||
|
|
||||||
|
def __init__(self, file="esbo-etc_defaults.xml"):
|
||||||
|
"""
|
||||||
|
Parse a XML configuration file.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
file : str
|
||||||
|
configuration file to parse
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check if configuration file exists
|
||||||
|
if not os.path.exists(file):
|
||||||
|
error("Configuration file '" + file + "' doesn't exist.")
|
||||||
|
|
||||||
|
# Read configuration file
|
||||||
|
logging.info("Reading configuration from file '" + file + "'.")
|
||||||
|
self.conf = self.parser(eT.parse(file).getroot())
|
||||||
|
|
||||||
|
self.check_config(self.conf)
|
||||||
|
self.calc_metaoptions()
|
||||||
|
|
||||||
|
def parser(self, parent: eT.Element):
|
||||||
|
"""
|
||||||
|
Parse a XML element tree to an Entry-tree
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
parent : xml.etree.ElementTree.Element
|
||||||
|
The parent XML tree to be parsed
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
obj : Entry
|
||||||
|
The parsed XML tree
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Initialize empty Entry object
|
||||||
|
obj = Entry()
|
||||||
|
|
||||||
|
for child in parent:
|
||||||
|
# recursively parse children of child element
|
||||||
|
parsed_child = self.parser(child)
|
||||||
|
# parse attributes of child element
|
||||||
|
parsed_child.parse(child)
|
||||||
|
|
||||||
|
# Add or append the parsed child to the prepared Entry object
|
||||||
|
if hasattr(obj, child.tag):
|
||||||
|
if isinstance(getattr(obj, child.tag), list):
|
||||||
|
getattr(obj, child.tag).append(parsed_child)
|
||||||
|
else:
|
||||||
|
setattr(obj, child.tag, [getattr(obj, child.tag), parsed_child])
|
||||||
|
else:
|
||||||
|
setattr(obj, child.tag, parsed_child)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def calc_metaoptions(self):
|
||||||
|
"""
|
||||||
|
Calculate additional attributes e.g. the wavelength grid
|
||||||
|
"""
|
||||||
|
self.calc_metaoption_wl_delta()
|
||||||
|
|
||||||
|
def calc_metaoption_wl_delta(self):
|
||||||
|
"""
|
||||||
|
Calculate the wavelength grid used for the calculations.
|
||||||
|
"""
|
||||||
|
if hasattr(self.conf.common, "wl_delta"):
|
||||||
|
wl_delta = self.conf.common.wl_delta()
|
||||||
|
else:
|
||||||
|
wl_delta = self.conf.common.wl_min() / self.conf.common.res()
|
||||||
|
setattr(self.conf.common, 'wl_bins',
|
||||||
|
Entry(val=np.append(np.arange(self.conf.common.wl_min().to(u.nm).value,
|
||||||
|
self.conf.common.wl_max().to(u.nm).value, wl_delta.to(u.nm).value),
|
||||||
|
self.conf.common.wl_max().to(u.nm).value) << u.nm))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_config(conf: Entry):
|
||||||
|
# Check common
|
||||||
|
if not hasattr(conf, "common"):
|
||||||
|
error("Configuration check: Missing required container 'common'.")
|
||||||
|
if not hasattr(conf.common, "wl_min"):
|
||||||
|
error("Configuration check: common: Missing required container 'wl_min'.")
|
||||||
|
mes = conf.common.wl_min.check_quantity("val", u.m)
|
||||||
|
mes is not None and error("Configuration check: common -> wl_min: " + mes)
|
||||||
|
if not hasattr(conf.common, "wl_max"):
|
||||||
|
error("Configuration check: common: Missing required container 'wl_max'.")
|
||||||
|
mes = conf.common.wl_max.check_quantity("val", u.m)
|
||||||
|
mes is not None and error("Configuration check: common -> wl_max: " + mes)
|
||||||
|
if hasattr(conf.common, "wl_delta"):
|
||||||
|
mes = conf.common.wl_delta.check_quantity("val", u.m)
|
||||||
|
mes is not None and error("Configuration check: common -> wl_delta: " + mes)
|
||||||
|
elif hasattr(conf.common, "res"):
|
||||||
|
mes = conf.common.res.check_quantity("val", u.dimensionless_unscaled)
|
||||||
|
mes is not None and error("Configuration check: common -> res: " + mes)
|
||||||
|
else:
|
||||||
|
error("Configuration check: common: Expected one of the containers 'wl_delta' or 'res' but got none.")
|
||||||
|
if not hasattr(conf.common, "d_aperture"):
|
||||||
|
error("Configuration check: common: Missing required container 'd_aperture'.")
|
||||||
|
mes = conf.common.d_aperture.check_quantity("val", u.m)
|
||||||
|
mes is not None and error("Configuration check: common -> d_aperture: " + mes)
|
||||||
|
if not hasattr(conf.common, "psf"):
|
||||||
|
setattr(conf.common, "psf", Entry(val="Airy"))
|
||||||
|
else:
|
||||||
|
if conf.common.psf().lower() != "airy":
|
||||||
|
mes = conf.common.psf.check_file("val")
|
||||||
|
mes is not None and error("Configuration check: common -> psf: " + mes)
|
||||||
|
if hasattr(conf.common, "jitter_sigma"):
|
||||||
|
mes = conf.common.jitter_sigma.check_quantity("val", u.arcsec)
|
||||||
|
mes is not None and error("Configuration check: common -> jitter_sigma: " + mes)
|
||||||
|
if not hasattr(conf.common, "output_path"):
|
||||||
|
setattr(conf.common, "output_path", Entry(val="."))
|
||||||
|
|
||||||
|
# Check astroscene
|
||||||
|
if not hasattr(conf, "astroscene"):
|
||||||
|
error("Configuration check: Missing required container 'astroscene'.")
|
||||||
|
if not hasattr(conf.astroscene, "target"):
|
||||||
|
error("Configuration check: astroscene: Missing required container 'target'.")
|
||||||
|
if not hasattr(conf.astroscene.target, "type"):
|
||||||
|
error("Configuration check: astroscene -> target: Missing required parameter 'type'.")
|
||||||
|
if conf.astroscene.target.type not in dir(tg):
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
error("Configuration check: astroscene -> target: Target type '" + conf.astroscene.target.type +
|
||||||
|
"' does not exist. Did you mean '" + difflib.get_close_matches(conf.astroscene.target.type,
|
||||||
|
dir(tg), 1)[0] + "'?")
|
||||||
|
mes = getattr(tg, conf.astroscene.target.type).check_config(conf.astroscene.target)
|
||||||
|
mes is not None and error("Configuration check: astroscene -> target: " + mes)
|
||||||
|
|
||||||
|
def check_optical_components(conf: Entry):
|
||||||
|
if hasattr(conf, "optical_component"):
|
||||||
|
for component in (conf.optical_component if type(conf.optical_component) == list else
|
||||||
|
[conf.optical_component]):
|
||||||
|
if not hasattr(component, "type"):
|
||||||
|
return "optical_component: Missing required parameter 'type'."
|
||||||
|
if component.type not in dir(oc):
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
return "optical_component: optical component type '" + component.type + \
|
||||||
|
"' does not exist. Did you mean '" + \
|
||||||
|
difflib.get_close_matches(component.type, dir(tg), 1)[0] + "'?"
|
||||||
|
mes = getattr(oc, component.type).check_config(component)
|
||||||
|
if mes is not None:
|
||||||
|
print(component.type)
|
||||||
|
print(mes)
|
||||||
|
return "optical_component -> " + component.type + ": " + mes
|
||||||
|
|
||||||
|
mes = check_optical_components(conf.astroscene)
|
||||||
|
mes is not None and error("Configuration check: astroscene -> " + mes)
|
||||||
|
|
||||||
|
# Check common_optics
|
||||||
|
if hasattr(conf, "common_optics"):
|
||||||
|
mes = check_optical_components(conf.common_optics)
|
||||||
|
mes is not None and error("Configuration check: common_optics -> " + mes)
|
||||||
|
|
||||||
|
# Check instrument
|
||||||
|
if not hasattr(conf, "instrument"):
|
||||||
|
error("Configuration check: Missing required container 'instrument'.")
|
||||||
|
mes = check_optical_components(conf.instrument)
|
||||||
|
mes is not None and error("Configuration check: instrument -> " + mes)
|
||||||
|
if not hasattr(conf.instrument, "sensor"):
|
||||||
|
error("Configuration check: instrument: Missing required container 'sensor'.")
|
||||||
|
if not hasattr(conf.instrument.sensor, "type"):
|
||||||
|
error("Configuration check: instrument -> target: Missing required parameter 'type'.")
|
||||||
|
if conf.instrument.sensor.type not in dir(sensor):
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
error("Configuration check: sensor -> target: Sensor type '" + conf.instrument.sensor.type +
|
||||||
|
"' does not exist. Did you mean '" + difflib.get_close_matches(conf.instrument.sensor.type,
|
||||||
|
dir(sensor), 1)[0] + "'?")
|
||||||
|
mes = getattr(sensor, conf.instrument.sensor.type).check_config(conf.instrument.sensor)
|
||||||
|
mes is not None and error("Configuration check: instrument -> sensor: " + mes)
|
106
esbo_etc/classes/Entry.py
Normal file
106
esbo_etc/classes/Entry.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
from typing import Union
|
||||||
|
import re
|
||||||
|
import xml.etree.ElementTree as eT
|
||||||
|
import astropy.units as u
|
||||||
|
from ..lib.helpers import error
|
||||||
|
import difflib
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class Entry(object):
|
||||||
|
"""
|
||||||
|
A class used to represent a configuration entry.
|
||||||
|
Taken from ExoSim (https://github.com/ExoSim/ExoSimPublic)
|
||||||
|
"""
|
||||||
|
val: Union[str, bool, u.Quantity]
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
self.__setattr__(key, value)
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
return self.val if hasattr(self, "val") else None
|
||||||
|
|
||||||
|
def parse(self, xml: eT.Element):
|
||||||
|
"""
|
||||||
|
Parse attributes of a XML element
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
xml : xml.etree.ElementTree.Element
|
||||||
|
XML element to parse the attributes from
|
||||||
|
"""
|
||||||
|
# Copy the XML attributes to object attributes
|
||||||
|
for attrib in xml.attrib.keys():
|
||||||
|
setattr(self, attrib, xml.attrib[attrib])
|
||||||
|
# parse units
|
||||||
|
attribs = list(xml.attrib.keys())
|
||||||
|
units = list(filter(re.compile(".*_unit$").match, attribs))
|
||||||
|
for unit in units:
|
||||||
|
var = unit.replace("_unit", "")
|
||||||
|
if hasattr(self, var):
|
||||||
|
try:
|
||||||
|
val = u.Quantity(list(map(float, getattr(self, var).split(','))), getattr(self, unit))
|
||||||
|
if len(val) == 1:
|
||||||
|
val = val[0]
|
||||||
|
setattr(self, var, val)
|
||||||
|
except (ValueError, LookupError):
|
||||||
|
error("unable to convert units in entry '" + xml.tag + "': " + getattr(self, var) + " " +
|
||||||
|
getattr(self, unit), exit_=False)
|
||||||
|
# Convert boolean values
|
||||||
|
if hasattr(self, "val") and type(self.val) == str and self.val.lower() in ["false", "true"]:
|
||||||
|
self.val = (self.val.lower() == "true")
|
||||||
|
|
||||||
|
def check_quantity(self, name: str, unit: u.Unit) -> Union[None, str]:
|
||||||
|
if not hasattr(self, name):
|
||||||
|
return "Parameter '" + name + "' not found."
|
||||||
|
attr = getattr(self, name)
|
||||||
|
if type(attr) != u.Quantity:
|
||||||
|
if unit == u.dimensionless_unscaled:
|
||||||
|
try:
|
||||||
|
self.__setattr__(name, float(attr) * u.dimensionless_unscaled)
|
||||||
|
except ValueError:
|
||||||
|
return "Expected parameter '" + name + "' with unit '" + unit.to_string() + \
|
||||||
|
"' but got no unit and cannot convert '" + attr + "' to a numeric value."
|
||||||
|
else:
|
||||||
|
return "Expected parameter '" + name + "' with unit '" + unit.to_string() + "' but got no unit."
|
||||||
|
if not attr.unit.is_equivalent(unit):
|
||||||
|
return "Expected parameter '" + name + "' with unit equivalent to'" + unit.to_string() + \
|
||||||
|
"' but got unit '" + attr.unit.to_string() + "'."
|
||||||
|
return None
|
||||||
|
|
||||||
|
def check_selection(self, name, choices: list) -> Union[None, str]:
|
||||||
|
if not hasattr(self, name):
|
||||||
|
return "Parameter '" + name + "' not found."
|
||||||
|
attr = getattr(self, name)
|
||||||
|
if type(attr) != str:
|
||||||
|
return "Expected parameter '" + name + "' to be of type string."
|
||||||
|
if attr not in choices:
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
return "Value '" + attr + "' not allowed for parameter '" + name + "'. Did you mean '" +\
|
||||||
|
difflib.get_close_matches(attr, choices, 1)[0] + "'?"
|
||||||
|
return None
|
||||||
|
|
||||||
|
def check_file(self, name) -> Union[None, str]:
|
||||||
|
if not hasattr(self, name):
|
||||||
|
return "Parameter '" + name + "' not found."
|
||||||
|
if not os.path.isfile(getattr(self, name)):
|
||||||
|
return "File '" + getattr(self, name) + "' does not exist."
|
||||||
|
|
||||||
|
def check_float(self, name) -> Union[None, str]:
|
||||||
|
if not hasattr(self, name):
|
||||||
|
return "Parameter '" + name + "' not found."
|
||||||
|
attr = getattr(self, name)
|
||||||
|
if type(attr) == float:
|
||||||
|
return None
|
||||||
|
elif type(attr) == u.Quantity:
|
||||||
|
setattr(self, name, attr.value)
|
||||||
|
elif type(attr) == str:
|
||||||
|
try:
|
||||||
|
setattr(self, name, float(attr))
|
||||||
|
except ValueError:
|
||||||
|
return "Cannot convert parameter '" + name + "' with value '" + attr + "' to a numeric value."
|
||||||
|
elif type(attr) == int:
|
||||||
|
setattr(self, name, float(attr))
|
||||||
|
else:
|
||||||
|
return "Expected parameter '" + name + "' to be numeric but got '" + type(attr) + "' instead."
|
@ -1,4 +1,4 @@
|
|||||||
from .config import *
|
from .Config import *
|
||||||
from .IRadiant import *
|
from .IRadiant import *
|
||||||
from .SpectralQty import *
|
from .SpectralQty import *
|
||||||
from .target import *
|
from .target import *
|
||||||
|
@ -1,139 +0,0 @@
|
|||||||
import xml.etree.ElementTree as eT
|
|
||||||
import numpy as np
|
|
||||||
import astropy.units as u
|
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
from ..lib.helpers import error
|
|
||||||
from typing import Union
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
class Entry(object):
|
|
||||||
"""
|
|
||||||
A class used to represent a configuration entry.
|
|
||||||
Taken from ExoSim (https://github.com/ExoSim/ExoSimPublic)
|
|
||||||
"""
|
|
||||||
val: Union[str, bool, u.Quantity]
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
for key, value in kwargs.items():
|
|
||||||
self.__setattr__(key, value)
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
return self.val if hasattr(self, "val") else None
|
|
||||||
|
|
||||||
def parse(self, xml: eT.Element):
|
|
||||||
"""
|
|
||||||
Parse attributes of a XML element
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
xml : xml.etree.ElementTree.Element
|
|
||||||
XML element to parse the attributes from
|
|
||||||
"""
|
|
||||||
# Copy the XML attributes to object attributes
|
|
||||||
for attrib in xml.attrib.keys():
|
|
||||||
setattr(self, attrib, xml.attrib[attrib])
|
|
||||||
# parse units
|
|
||||||
attribs = list(xml.attrib.keys())
|
|
||||||
units = list(filter(re.compile(".*_unit$").match, attribs))
|
|
||||||
for unit in units:
|
|
||||||
var = unit.replace("_unit", "")
|
|
||||||
if hasattr(self, var):
|
|
||||||
try:
|
|
||||||
val = u.Quantity(list(map(float, getattr(self, var).split(','))), getattr(self, unit))
|
|
||||||
if len(val) == 1:
|
|
||||||
val = val[0]
|
|
||||||
setattr(self, var, val)
|
|
||||||
except (ValueError, LookupError):
|
|
||||||
error("unable to convert units in entry '" + xml.tag + "': " + getattr(self, var) + " " +
|
|
||||||
getattr(self, unit), exit_=False)
|
|
||||||
# Convert boolean values
|
|
||||||
if hasattr(self, "val") and type(self.val) == str and self.val.lower() in ["false", "true"]:
|
|
||||||
self.val = (self.val.lower() == "true")
|
|
||||||
|
|
||||||
|
|
||||||
class Configuration(object):
|
|
||||||
"""
|
|
||||||
A Class to parse the XML configuration file.
|
|
||||||
Adapted from ExoSim (https://github.com/ExoSim/ExoSimPublic)
|
|
||||||
|
|
||||||
Attributes
|
|
||||||
----------
|
|
||||||
conf : Entry
|
|
||||||
Parsed configuration file as Entry-tree
|
|
||||||
"""
|
|
||||||
conf = None
|
|
||||||
|
|
||||||
def __init__(self, file="esbo-etc_defaults.xml"):
|
|
||||||
"""
|
|
||||||
Parse a XML configuration file.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
file : str
|
|
||||||
configuration file to parse
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Check if configuration file exists
|
|
||||||
if not os.path.exists(file):
|
|
||||||
error("Configuration file '" + file + "' doesn't exist.")
|
|
||||||
|
|
||||||
# Read configuration file
|
|
||||||
logging.info("Reading configuration from file '" + file + "'.")
|
|
||||||
self.conf = self.parser(eT.parse(file).getroot())
|
|
||||||
|
|
||||||
self.calc_metaoptions()
|
|
||||||
|
|
||||||
def parser(self, parent: eT.Element):
|
|
||||||
"""
|
|
||||||
Parse a XML element tree to an Entry-tree
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
parent : xml.etree.ElementTree.Element
|
|
||||||
The parent XML tree to be parsed
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
obj : Entry
|
|
||||||
The parsed XML tree
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Initialize empty Entry object
|
|
||||||
obj = Entry()
|
|
||||||
|
|
||||||
for child in parent:
|
|
||||||
# recursively parse children of child element
|
|
||||||
parsed_child = self.parser(child)
|
|
||||||
# parse attributes of child element
|
|
||||||
parsed_child.parse(child)
|
|
||||||
|
|
||||||
# Add or append the parsed child to the prepared Entry object
|
|
||||||
if hasattr(obj, child.tag):
|
|
||||||
if isinstance(getattr(obj, child.tag), list):
|
|
||||||
getattr(obj, child.tag).append(parsed_child)
|
|
||||||
else:
|
|
||||||
setattr(obj, child.tag, [getattr(obj, child.tag), parsed_child])
|
|
||||||
else:
|
|
||||||
setattr(obj, child.tag, parsed_child)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def calc_metaoptions(self):
|
|
||||||
"""
|
|
||||||
Calculate additional attributes e.g. the wavelength grid
|
|
||||||
"""
|
|
||||||
self.calc_metaoption_wl_delta()
|
|
||||||
|
|
||||||
def calc_metaoption_wl_delta(self):
|
|
||||||
"""
|
|
||||||
Calculate the wavelength grid used for the calculations.
|
|
||||||
"""
|
|
||||||
if hasattr(self.conf.common, "wl_delta"):
|
|
||||||
wl_delta = self.conf.common.wl_delta()
|
|
||||||
else:
|
|
||||||
wl_delta = self.conf.common.wl_min() / self.conf.common.res()
|
|
||||||
setattr(self.conf.common, 'wl_bins',
|
|
||||||
Entry(val=np.append(np.arange(self.conf.common.wl_min().to(u.nm).value,
|
|
||||||
self.conf.common.wl_max().to(u.nm).value, wl_delta.to(u.nm).value),
|
|
||||||
self.conf.common.wl_max().to(u.nm).value) << u.nm))
|
|
Loading…
Reference in New Issue
Block a user