Source code for isofit.configs.configs

#! /usr/bin/env python3
#
#  Copyright 2018 California Institute of Technology
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
# ISOFIT: Imaging Spectrometer Optimal FITting
# Author: Philip G. Brodrick, philip.brodrick@jpl.nasa.gov

import logging
import os
from collections import OrderedDict
from typing import Dict, List, Type

import yaml

from isofit.configs.base_config import BaseConfigSection
from isofit.configs.sections.forward_model_config import ForwardModelConfig
from isofit.configs.sections.implementation_config import ImplementationConfig
from isofit.configs.sections.input_config import InputConfig
from isofit.configs.sections.output_config import OutputConfig
from isofit.core import common


[docs] class Config(BaseConfigSection): """ Handles the reading and formatting of configuration files. Please note - there are many ways to do this, some of which require fewer lines of code. This method was chosen to facilitate more clarity when using / adding / modifying code, particularly given the highly flexible nature of Isofit. How to use: To add an additional parameter to an existing class, simply go to the relevant config (e.g. for forward_model go to sections/forward_model_config.py), and in the config class (e.g. ForwardModelConfig) add the parameter. Also Add a hidden parameter with the _type suffix, which will be used to check that configs read the appropriate type. Add comments directly below, to be auto-appended to online documentation. Example:: class GenericConfigSection(BaseConfigSection): _attribute_type = str attribute = 'my attribute' \"""str: attribute does whatever it happens to do\""" To validate that attributes have appropriate relationships or characteristics, use the hidden _check_config_validity method to add more detailed validation checks. Simply return a list of string descriptions of errors from the method as demonstrated:: def _check_config_validity(self) -> List[str]: errors = list() if self.attribute_min >= self.attribute_max: errors.append('attribute_min must be less than attribute_max.') return errors """ def __init__(self, configdict) -> None: self._input_type = InputConfig self.input = InputConfig({}) """InputConfig: Input config. Holds all input file information. """ self._output_type = OutputConfig self.output = OutputConfig({}) """OutputConfig: Output config. Holds all output file information. """ self._forward_model_type = ForwardModelConfig self.forward_model = ForwardModelConfig({}) """ForwardModelConfig: forward_model config. Holds information about surface models, radiative transfer models, and the instrument. """ self._implementation_type = ImplementationConfig self.implementation = ImplementationConfig({}) """ImplementationConfig: holds information regarding how isofit is to be run, including relevant sub-configs (e.g. inversion information). """ # Load sub-classes and attributes self.set_config_options(configdict)
[docs] def get_config_as_dict(self) -> dict: """Get configuration options as a nested dictionary with delineated sections. Returns: Configuration options as a nested dictionary with delineated sections. """ config = OrderedDict() for config_section in self._get_nontype_attributes(): populated_section = getattr(self, config_section) config[config_section] = populated_section.get_config_options_as_dict() return config
[docs] def get_config_errors(self): """ Get configuration option errors by checking the validity of each config section. """ logging.info("Checking config sections for configuration issues") errors = self.check_config_validity() errors.extend(self.check_inter_section_validity()) for e in errors: logging.error(e) if len(errors) > 0: raise AttributeError("Configuration error(s) found. See log for details.") logging.info("Configuration file checks complete, no errors found.")
[docs] def check_inter_section_validity(self): return_errors = [] if ( self.implementation.mode == "simulation" and self.input.reflectance_file is None ): return_errors.append( "If implementation.mode is set to simulation, input.reflectance_file" " must be set" ) return return_errors
[docs] def get_config_differences(config_a: Config, config_b: Config) -> Dict: differing_items = dict() dict_a = config_a.get_config_as_dict() dict_b = config_b.get_config_as_dict() all_sections = set(list(dict_a.keys()) + list(dict_b.keys())) for section in all_sections: section_a = dict_a.get(section, dict()) section_b = dict_b.get(section, dict()) all_options = set(list(section_a.keys()) + list(section_b.keys())) for option in all_options: value_a = section_a.get(option, None) value_b = section_b.get(option, None) if value_a != value_b: logging.debug( "Configs have different values for option {} in section {}: {}" " and {}".format(option, section, value_a, value_b) ) differing_items.setdefault(section, dict())[option] = (value_a, value_b) return differing_items
[docs] def create_new_config(config_file: str) -> Config: """Load a config file from disk. Args: config_file: file to load config from. Currently accepted formats: JSON and YAML Returns: Config object, having completed all necessary config checks """ # pdb.set_trace() try: with open(config_file, "r") as f: # pdb.set_trace() config_dict = yaml.safe_load(f) except: raise IOError( "Unexpected configuration file time, only json and yaml supported" ) configdir, f = os.path.split(os.path.abspath(config_file)) config_dict = common.expand_all_paths(config_dict, configdir) config = Config(config_dict) return config