# Licensed under a 3-clause BSD style license - see LICENSE.rst
import re
import numpy as np
from .common import _get_content_as_lines
__all__ = ['read_characteristics']
def transform_ODB_SI_ALIGN(values):
"""
Transform the raw tuple of ODB_SI_ALIGN values into a structure suitable
for Python manipulation.
:param values: tuple
:returns: dictionary of matrices, one per detector in C/Python order
"""
values = np.array(values).reshape(4, 3, 3)
si_align = {'ACIS-I': values[0].transpose(),
'ACIS-S': values[1].transpose(),
'HRC-I': values[2].transpose(),
'HRC-S': values[3].transpose()}
return si_align
[docs]
def read_characteristics(content='CHARACTERIS_12OCT15', item=None, raw=False):
"""
Read OFLS ODB CHARACTERISTICS file.
If ``item`` is supplied then only that item is returns.
By default any available transformation routines will be applied. Transform routines
are any function named ``transform_<item>``. This can be disabled by setting
``raw=True`` to get raw values.
:param content: OFLS ODB characteristics file name or lines
:param item: return only the ``item`` characteristic
:param raw: return raw values instead of transforming where possible
:returns: dictionary of characteristic values
"""
lines = _get_content_as_lines(content)
# Remove comments and whitespace
lines = (line for line in lines if not (line.startswith('C') or
re.match(r'\s*[$]', line)))
lines = (re.sub(r'!.*', '', line).strip() for line in lines)
lines = [line for line in lines if line]
# Drop zeros initialization for ODB_OBC_SCP_MEM, otherwise code fails because it ends
# up trying to set a tuple.
lines = [line for line in lines if not line.startswith('ODB_OBC_SCP_MEM = 1024*0')]
# If getting just an item then keep only relevant lines
if item is not None:
new_lines = []
for line in lines:
if new_lines and line.startswith('ODB'):
lines = new_lines
break
if new_lines or line.startswith(item):
new_lines.append(line)
last_line = len(lines) - 1
dict_vars = set()
re_mult = re.compile(r'(\d+) \* ([^,]+,)', re.VERBOSE)
# NOTE: a much better way to do this would be to collect up the lines into a single
# string for each key and then use eval(). This requires a little change in logic as
# well as parsing the indices. Next time!
for i, line in enumerate(lines):
if i == last_line:
continue
# Fix a couple of errors in baseline characteristics
if line == '1,2,4, 1,2,5, 1,2,6, 1,3,4 1,4,6,':
line = '1,2,4, 1,2,5, 1,2,6, 1,3,4, 1,4,6,'
if line.endswith('9,17,3, 10,19,4, 9,19,3, 10,20,4, 9,16,3, 9,17,3'):
line = line + ','
# Change Fortran D-style exponents to E
line = re.sub(r'([\d.])D([-+\d])', r'\1E\2', line)
# Replace multiplier syntax with expanded version. E.g. 3*0.0, = > 0.0, 0.0, 0.0,
while True:
match = re_mult.search(line)
if match:
mult, val = match.groups()
line = re_mult.sub(val * int(mult), line)
else:
break
# If the next line is not an ODB specifier then append a Python continuation char
if not lines[i + 1].startswith('ODB'):
line = line + '\\'
# Turn ODB declaration into a Python declaration setting a key value
# in the global `ODB` dict.
if line.startswith('ODB'):
match = re.match(r'(ODB_ \w+) '
r'( \( [\d,\s]+ \) )?'
r' \s* = (.*)',
line, re.VERBOSE)
if not match:
raise ValueError('Could not parse line\n{}'.format(line))
name, indices, value = match.groups()
if indices:
dict_vars.add(name)
line = 'ODB["{}"][{}] ={}'.format(name, indices[1:-1], value)
else:
line = 'ODB["{}"] ={}'.format(name, value)
lines[i] = line
declare_dicts = ['ODB["{}"] = dict()'.format(var) for var in dict_vars]
lines = ['ODB = dict()'] + declare_dicts + lines
# This sets `ODB` locally. Note: cannot define ODB (e.g. ODB=None) to fix flymake
# highlighting. For some reason this prevents exec statement from working.
namespace = {}
exec('\n'.join(lines), namespace)
ODB = namespace['ODB']
for key, val in ODB.items():
if isinstance(val, tuple) and len(val) == 1:
ODB[key] = val[0]
if not raw:
transform_func = 'transform_{}'.format(key)
if transform_func in globals():
ODB[key] = globals()[transform_func](val)
return ODB[item] if item else ODB