Source code for spectractor.config

import configparser
import os
import re
import shutil

import astropy.units.quantity
import numpy as np
import logging
import astropy.units as units
from astropy import constants as const

from spectractor import parameters

if not parameters.CALLING_CODE:
    import coloredlogs

logging.getLogger("matplotlib").setLevel(logging.ERROR)
logging.getLogger("numba").setLevel(logging.ERROR)
logging.getLogger("h5py").setLevel(logging.ERROR)


[docs] def from_config_to_dict(path): """Convert config file keywords into dictionnary. Parameters ---------- path: str The path to the config file. Examples -------- >>> mypath = os.path.dirname(__file__) >>> out = from_config_to_dict(os.path.join(mypath, "../config/", "default.ini")) >>> assert type(out) == dict """ # List all contents config = configparser.ConfigParser() config.read(path) out = {} for section in config.sections(): out[section] = {} for options in config.options(section): value = config.get(section, options) if re.match(r"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?", value): if ' ' in value: value = str(value) elif '.' in value or 'e' in value: value = float(value) else: value = int(value) elif value == 'True' or value == 'False': value = config.getboolean(section, options) else: value = str(value) out[section][options] = value return out
[docs] def from_config_to_parameters(path): """Convert config file keywords into spectractor.parameters parameters. Parameters ---------- path: str The path to the config file. Examples -------- >>> mypath = os.path.dirname(__file__) >>> from_config_to_parameters(os.path.join(mypath, parameters.CONFIG_DIR, "default.ini")) >>> assert parameters.OBS_NAME == "DEFAULT" """ # List all contents d = from_config_to_dict(path) for section in d.keys(): for options in d[section].keys(): setattr(parameters, options.upper(), d[section][options])
[docs] def load_config(config_filename, rebin=True): """Load configuration parameters from a .ini config file. Parameters ---------- config_filename: str The path to the config file. rebin: bool, optional If True, the parameters.REBIN parameter is used and every parameters are changed to comply with the REBIN value. If False, the parameters.REBIN parameter is skipped and set to 1. Examples -------- >>> parameters.VERBOSE = True >>> load_config("./config/ctio.ini") >>> assert parameters.OBS_NAME == "CTIO" .. doctest: :hide: >>> load_config("./config/ctio.ini") >>> load_config("ctio.ini") >>> load_config("./config/unknown_file.ini") #doctest: +ELLIPSIS Traceback (most recent call last): ... FileNotFoundError: Config file ./config/unknown_file.ini does not exist. """ my_logger = set_logger(__name__) mypath = os.path.dirname(__file__) if not os.path.isfile(os.path.join(mypath, parameters.CONFIG_DIR, "default.ini")): raise FileNotFoundError('Config file default.ini does not exist.') # Load the configuration file from_config_to_parameters(os.path.join(mypath, parameters.CONFIG_DIR, "default.ini")) if not os.path.isfile(config_filename): if not os.path.isfile(os.path.join(mypath, parameters.CONFIG_DIR, config_filename)): raise FileNotFoundError(f'Config file {config_filename} does not exist.') else: config_filename = os.path.join(mypath, parameters.CONFIG_DIR, config_filename) # Load the configuration file my_logger.info(f"\n\tLoading {config_filename} with {parameters.VERBOSE=}...") from_config_to_parameters(config_filename) # Derive other parameters update_derived_parameters() # Apply rebinning if parameters.CCD_REBIN > 1 and rebin: apply_rebinning_to_parameters() else: parameters.CCD_REBIN = 1 # check consistency if parameters.PIXWIDTH_BOXSIZE > parameters.PIXWIDTH_BACKGROUND: raise ValueError(f'parameters.PIXWIDTH_BOXSIZE must be smaller than parameters.PIXWIDTH_BACKGROUND (or equal).') # check consistency if parameters.PSF_POLY_TYPE not in ["polynomial", "legendre"]: raise ValueError(f'parameters.PSF_POLY_TYPE must be either "polynomial" or "legendre". Got {parameters.PSF_POLY_TYPE=}') # check presence of atmospheric simulation packages if parameters.SPECTRACTOR_ATMOSPHERE_SIM.lower() not in ["none", "libradtran", "getobsatmo"]: raise ValueError(f'parameters.SPECTRACTOR_ATMOSPHERE_SIM must be either ["none", "libradtran", "getobsatmo"]. ' f'Got {parameters.SPECTRACTOR_ATMOSPHERE_SIM}.') if parameters.SPECTRACTOR_ATMOSPHERE_SIM.lower() == "libradtran": if not shutil.which("uvspec") and not os.path.isfile(os.path.join(parameters.LIBRADTRAN_DIR, 'bin/uvspec')): raise OSError(f"{parameters.SPECTRACTOR_ATMOSPHERE_SIM=} but uvspec executable not found in $PATH " f"or {os.path.join(parameters.LIBRADTRAN_DIR, 'bin/')}") if parameters.SPECTRACTOR_ATMOSPHERE_SIM.lower() == "getobsatmo": try: import getObsAtmo except ModuleNotFoundError: raise ModuleNotFoundError(f"{parameters.SPECTRACTOR_ATMOSPHERE_SIM=} but getObsAtmo module not found.") # verbosity if parameters.VERBOSE or parameters.DEBUG: txt = "" # default.ini should be the config file with the most parameters config = configparser.ConfigParser() config.read(os.path.join(mypath, parameters.CONFIG_DIR, "default.ini")) for section in config.sections(): txt += f"Section: {section}\n" for options in config.options(section): value = config.get(section, options) par = getattr(parameters, options.upper()) txt += f"x {options}: {value}\t=> parameters.{options.upper()}: {par}\t {type(par)}\n" my_logger.info(f"Loaded {config_filename} with\n{txt}")
[docs] def update_derived_parameters(): # Derive other parameters parameters.CALIB_BGD_NPARAMS = parameters.CALIB_BGD_ORDER + 1 parameters.LAMBDAS = np.arange(parameters.LAMBDA_MIN, parameters.LAMBDA_MAX, parameters.LAMBDA_STEP) if not isinstance(parameters.OBS_SURFACE, astropy.units.quantity.Quantity): parameters.OBS_SURFACE *= units.cm ** 2 # Surface of telescope # Units of SEDs in flam (erg/s/cm2/nm) : SED_UNIT = 1 * units.erg / units.s / units.cm ** 2 / units.nanometer parameters.FLAM_TO_ADURATE = ((parameters.OBS_SURFACE * SED_UNIT * units.s * units.nanometer ** 2 / (const.h * const.c) / parameters.CCD_GAIN).decompose()).value
[docs] def apply_rebinning_to_parameters(): """Divide or multiply original parameters by parameters.CCD_REBIN to set them correctly in the case of an image rebinning. Examples -------- >>> parameters.PIXWIDTH_SIGNAL = 40 >>> parameters.CCD_PIXEL2MM = 10 >>> parameters.CCD_REBIN = 2 >>> apply_rebinning_to_parameters() >>> parameters.PIXWIDTH_SIGNAL 20 >>> parameters.CCD_PIXEL2MM 20 """ # Apply rebinning parameters.PIXDIST_BACKGROUND = int(parameters.PIXDIST_BACKGROUND // parameters.CCD_REBIN) parameters.PIXWIDTH_BOXSIZE = int(max(10, parameters.PIXWIDTH_BOXSIZE // parameters.CCD_REBIN)) parameters.PIXWIDTH_BACKGROUND = int(parameters.PIXWIDTH_BACKGROUND // parameters.CCD_REBIN) parameters.PIXWIDTH_SIGNAL = int(parameters.PIXWIDTH_SIGNAL // parameters.CCD_REBIN) parameters.CCD_IMSIZE = int(parameters.CCD_IMSIZE // parameters.CCD_REBIN) parameters.CCD_PIXEL2MM *= parameters.CCD_REBIN parameters.CCD_PIXEL2ARCSEC *= parameters.CCD_REBIN parameters.XWINDOW = int(parameters.XWINDOW // parameters.CCD_REBIN) parameters.YWINDOW = int(parameters.YWINDOW // parameters.CCD_REBIN) parameters.XWINDOW_ROT = int(parameters.XWINDOW_ROT // parameters.CCD_REBIN) parameters.YWINDOW_ROT = int(parameters.YWINDOW_ROT // parameters.CCD_REBIN) parameters.PSF_PIXEL_STEP_TRANSVERSE_FIT = int(parameters.PSF_PIXEL_STEP_TRANSVERSE_FIT // parameters.CCD_REBIN) update_derived_parameters()
[docs] def set_logger(logger): """Logger function for all classes. Parameters ---------- logger: str Name of the class, usually self.__class__.__name__ Returns ------- my_logger: logging Logging object Examples -------- >>> class Test: ... def __init__(self): ... self.my_logger = set_logger(self.__class__.__name__) ... def log(self): ... self.my_logger.info('This info test function works.') ... self.my_logger.debug('This debug test function works.') ... self.my_logger.warning('This warning test function works.') >>> from spectractor import parameters >>> parameters.VERBOSE = True >>> parameters.DEBUG = True >>> test = Test() >>> test.log() """ my_logger = logging.getLogger(logger) my_format = "%(asctime)-20s %(name)-10s %(funcName)-20s %(levelname)-6s %(message)s" logging.basicConfig(format=my_format, level=logging.WARNING) if not parameters.CALLING_CODE: coloredlogs.DEFAULT_LEVEL_STYLES['warn'] = {'color': 'yellow'} coloredlogs.DEFAULT_FIELD_STYLES['levelname'] = {'color': 'white', 'bold': True} if parameters.VERBOSE > 0: my_logger.setLevel(logging.INFO) if not parameters.CALLING_CODE: coloredlogs.install(fmt=my_format, level=logging.INFO) else: my_logger.setLevel(logging.WARNING) if not parameters.CALLING_CODE: coloredlogs.install(fmt=my_format, level=logging.WARNING) if parameters.DEBUG_LOGGING: my_logger.setLevel(logging.DEBUG) if not parameters.CALLING_CODE: coloredlogs.install(fmt=my_format, level=logging.DEBUG) return my_logger
if __name__ == "__main__": import doctest doctest.testmod()