# Copyright(c) 2015 Association of Universities for Research in Astronomy, Inc.
# by James E.H. Turner.
"""
Some high-level utilities for direct use in scripts & processing functions
(or which depend on the high-level data representations used in NDMapper).
"""
from ndmapper.data import FileName, DataFile, DataFileList
from .calibrations import K_CALIBRATIONS
[docs]def convert_region(region, convention):
"""
Convert a NumPy- or FITS-style region string into a tuple of integers
and Python slice objects that is suitable for subscripting arrays.
Some corner cases (eg. involving '*', ':' or steps) currently behave
differently from Python/IRAF subscripting but should be fairly harmless.
Parameters
----------
region : str
Region string, eg. '100:110,50:60', '100,*' or ':,99'.
convention : str
Indexing convention used: 'NumPy' (default) or 'FITS'; case
insensitive.
"""
# Check arguments:
if not isinstance(region, basestring):
raise TypeError('region must be a string')
convention = convention.lower()
if convention not in ['numpy', 'fits']:
raise ValueError('convention must be NumPy or FITS')
# Apply appropriate syntax & indexing adjustments for convention used:
if convention == 'numpy':
nregion = region
order = slice(None, None, None)
orig = 0
elif convention == 'fits':
nregion = region.replace('*', ':')
order = slice(None, None, -1)
orig = 1
else:
raise ValueError('convention must be \'NumPy\' or \'FITS\'')
# Split region into a range for each axis:
axes = nregion.split(',')
# Parse sub-string for each axis into a range & convert to slice object:
slices = []
for axis in axes[order]:
err = False if axis else True # disallow empty string
vals = axis.split(':')
nvals = len(vals)
if nvals > 3:
err = True # disallow more than start:stop:step
elif nvals == 1:
try:
sliceobj = int(vals[0])-orig # single row/column number
except ValueError:
err = True
else:
try:
# Any adjustment for 1-based indexing is applied only to the
# start of the range, since 1 has to be added to the ending
# index to account for FITS/IRAF-style ranges being inclusive.
sliceobj = slice(*(int(val)-adj if val else None \
for val, adj in zip(vals, (orig, 0, 0))))
except ValueError:
err = True # disallow non-numeric values etc.
if err:
raise ValueError('failed to parse region: [%s]' % region)
slices.append(sliceobj)
return tuple(slices)
[docs]def to_filename_strings(objects, strip_names=True, strip_dirs=True,
use_cal_dict=False):
"""
Extract a list of filename strings from one or more `str`, `FileName` or
`DataFile` objects (or a `DataFileList`), by default removing any path and
processing suffix/prefixes. A calibration dictionary may also be given,
in the format produced by calibrations.init_cal_dict(), if the relevant
option is enabled. It is the caller's responsibility to ensure that the
string values are actually valid filenames.
This is typically used to reproduce base filenames for use in either
downloading or looking up external information about the files.
"""
# Convert any recognized single objects to a list (partly to ensure we
# don't inadvertently iterate over the NDData instances of a DataFile):
if isinstance(objects, (DataFile, FileName, basestring)):
objects = [objects]
# If the objects argument looks like a calibration dict, extract a list
# of unique constituent filenames from all the calibrations:
elif use_cal_dict and hasattr(objects, 'keys') and \
K_CALIBRATIONS in objects:
objects = list(set([fn for flist in \
objects[K_CALIBRATIONS].itervalues() \
for fn in (flist if hasattr(flist, '__iter__') else [])]))
# This must be list-like after the above:
if not hasattr(objects, '__iter__') or hasattr(objects, 'keys'):
raise ValueError('objects parameter has an unexpected type')
return [str(FileName(str(obj), strip=strip_names, \
dirname='' if strip_dirs else None)) \
for obj in objects]
[docs]def to_datafilelist(arg, mode=None):
"""
Derive a `DataFileList` object from one or a sequence of objects than can
be converted to filename strings (eg. `str`, `FileName` or `DataFile`).
The ``mode`` defaults to ``'read'`` when given one or more filenames and
to the existing mode for `DataFile` and `DataFileList`. Where all of the
inputs are existing `DataFile` instances, those are re-used by reference
(``mode`` permitting), instead of opening new copies.
Beware of feeding this inappropriate argument types, as almost anything can
be converted to `str`...
"""
# Ensure the input is in a list, for iteration:
if isinstance(arg, DataFile) or not hasattr(arg, '__iter__'):
arg = [arg]
# If passed existing DataFile(s), use them as the data argument for
# instantiation. Currently, DataFiles can only be re-used by reference if
# all elements have that type. This case also covers DataFileList, which
# needs re-instantating in case a new mode is specified.
if arg and all([isinstance(df, DataFile) for df in arg]):
outlist = DataFileList(data=arg, mode=mode)
# Otherwise, convert inputs to filename strings and instantiate with those.
# Should the acceptable types be enumerated to avoid strange results? That
# might impact user creativity in both good and a bad ways...
else:
mode = 'read' if mode is None else mode
outlist = DataFileList(filenames=[str(fn) for fn in arg], mode=mode)
return outlist