Source code for sofia_redux.instruments.forcast.hdrequirements

# Licensed under a 3-clause BSD style license - see LICENSE.rst

import os
import re

from astropy import log
import numpy
import pandas

import sofia_redux.instruments.forcast as drip
import sofia_redux.instruments.forcast.configuration as dripconfig

__all__ = ['parse_condition', 'hdrequirements']


[docs] def parse_condition(condition): r""" Parse a condition value defined in the keyword table Conditions in the keyword table are usually defined as single condition=(KEYWORD<comparison operator>value) statements. However, there is a facility to provide AND or OR type logic across multiple conditions. The order in which conditions are supplied is important, as OR conditions will contain a set of AND conditions. If all of the AND conditions in a single OR condition are satisfied then there is no need to check any of the other OR conditions and we can report that the full condition (the condition parameter) has passed. For example if condition string passed in was of the form:: <C1>&<C2>&<C3> | <C4><C5> If all of the conditions in the first OR condition set (C1, C2, and C3) were satisfied, then we do not need to check C4 or C5 since the condition has already been satisfied. If one or more of the conditions was not met in the first OR set, we would need to check that both C4 and C5 in the second OR set are satisfied in order to report whether the overall condition has been met. The return value of parse_condition examines the input <condition> string and returns an outer list of OR conditions containing an inner list of AND conditions. The above example would be converted to the form:: [[c1, c2, c3], [c4, c5]] where the lowercase c now represents one of the AND conditions as a tuple of the form:: (keyword, comparison operator, value) Parameters ---------- condition : str Of the form KEYWORD<comparison operator><value> Returns ------- list of list of tuple of str The outer list contains a list of 'OR' (\|, \|\|) conditions, which is a sublist or 'AND' (&, &&) conditions. i.e. 'OR' condition = [AND condition 1, AND condition 2,...] """ operator_map = {'<': '<', '<<': '<', # Less than '<=': '<=', '=<': '<=', # Less than or equal to '>=': '>=', '=>': '=>', # Greater than or equal to '>': '>', '>>': '>', # Greater than '=': '==', '==': '==', # Is equal to '!=': '!=', '<>': '!=', '=!': '!='} # Is not equal to regex = re.compile(r'([A-Z0-9]+)([!=<>]+)(.+)') or_conditions = [] or_list = [x.strip() for x in condition.split('|') if x.strip()] for oreq in or_list: and_conditions = [] and_list = [x.strip() for x in oreq.split('&') if x.strip()] for areq in and_list: match = regex.match(areq) if not match: log.error("Bad condition definition detected: %s" % condition) return keyword, operator, requirement = match.groups() if operator not in operator_map: log.error("Bad comparison operator detected: %s" % operator) return and_conditions.append( (keyword, operator_map[operator], requirement)) or_conditions.append(and_conditions) return or_conditions
[docs] def hdrequirements(kwfile=None): """ Returns a dataframe containing the header requirements If fkeydef is not defined then CALDATA will be checked in the configuration files (data/config_files/) providing a path to the input-key-definition.txt file that will be used. This following describes IDL data type codes mapped to Python data types: IDL PYTHON 0 None (maps to string in headers) 1 bool 2 int 3 int 4 float 5 float 6 complex 7 string Parameters ---------- kwfile : str File path to the keyword definition file. The default is input-key-definition.txt Returns ------- pandas.DataFrame """ default = pandas.DataFrame(columns=['name', 'condition', 'type', 'enum', 'min', 'max']) if kwfile is None: if dripconfig.configuration is None: dripconfig.load() workpath = dripconfig.configuration.get('caldata') if workpath is None: workpath = os.path.join(os.path.dirname(drip.__file__), 'data') kwfile = os.path.join(workpath, 'input-key-definition.txt') if not os.path.isfile(kwfile): log.warning('file does not exist: %s' % kwfile) return default log.info('using keyword file: %s' % kwfile) df = pandas.read_csv(kwfile, sep=r'\s+', comment=';', index_col=0) if len(df) == 0: log.warning('keyword file does not have keyword elements') return default type_map = {0: str, 1: bool, 2: int, 3: int, 4: float, 5: float, 6: complex, 7: str} df['type'] = df['type'].apply(lambda x: type_map[x]) df['enum'] = df['enum'].apply(lambda x: x.split('|') if x != '.' else []) df.loc[df['format'] == '.', 'format'] = '' df.insert(0, 'required', True) df.loc[df['condition'] == 0, 'required'] = False zeroes = df['condition'] == '0' df.loc[zeroes, 'required'] = False df.loc[zeroes | (df['condition'] == '1'), 'condition'] = '' conditions = [x for x in df['condition'].unique() if x] condition_map = dict(map(lambda x: (x, parse_condition(x)), conditions)) condition_map[''] = [] df['condition'] = df['condition'].apply(lambda x: condition_map[x]) df.loc[df['min'] == -9999, 'min'] = numpy.nan df.loc[df['max'] == -9999, 'max'] = numpy.nan return df