Source code for gammapy.catalog.core

# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""Source catalog and object base classes.
"""
from __future__ import absolute_import, division, print_function, unicode_literals
from collections import OrderedDict
import sys
from pprint import pprint
from astropy.extern import six
from astropy.utils import lazyproperty
from astropy.units import Quantity
from ..utils.array import _is_int

__all__ = [
    'SourceCatalog',
    'SourceCatalogObject',
]


[docs]class SourceCatalogObject(object): """Source catalog object. This class can be used directly, but it's mostly used as a base class for the other source catalog classes. The catalog data on this source is stored in the `source.data` attribute as on OrderedDict. The source catalog object is decoupled from the source catalog, it doesn't hold a reference back to it. The catalog table row index is stored in `_table_row_index` though, because it can be useful for debugging or display. """ _source_name_key = 'Source_Name' _source_index_key = 'catalog_row_index' def __init__(self, data): self.data = data @property def name(self): """Source name""" name = self.data[self._source_name_key] return name.strip() @property def index(self): """Row index of source in catalog""" return self.data[self._source_index_key]
[docs] def pprint(self, file=None): """Pretty-print source data""" if not file: file = sys.stdout pprint(self.data, stream=file)
# TODO: add methods to serialise to JSON and YAML # and also to quickly pretty-print output in that format for interactive use. # Maybe even add HTML output for IPython repr? # Or at to_table method?
[docs] def info(self): """ Print summary info about the object. """ print(self)
[docs]class SourceCatalog(object): """Generic source catalog. This class can be used directly, but it's mostly used as a base class for the other source catalog classes. This is a thin wrapper around `~astropy.table.Table`, which is stored in the ``catalog.table`` attribute. Parameters ---------- table : `~astropy.table.Table` Table with catalog data. source_name_key : str Column with source name information source_name_alias : tuple of str Columns with source name aliases. This will allow accessing the source row by alias names as well. """ source_object_class = SourceCatalogObject # TODO: at the moment these are duplicated in SourceCatalogObject. # Should we share them somehow? _source_index_key = 'catalog_row_index' def __init__(self, table, source_name_key='Source_Name', source_name_alias=()): self.table = table self._source_name_key = source_name_key self._source_name_alias = source_name_alias @lazyproperty def _name_to_index_cache(self): # Make a dict for quick lookup: source name -> row index names = dict() for idx, row in enumerate(self.table): name = row[self._source_name_key] names[name.strip()] = idx for alias_column in self._source_name_alias: for alias in row[alias_column].split(','): if not alias == '': names[alias.strip()] = idx return names
[docs] def row_index(self, name): """Look up row index of source by name. Parameters ---------- name : str Source name Returns ------- index : int Row index of source in table """ index = self._name_to_index_cache[name] row = self.table[index] # check if name lookup is correct other wise recompute _name_to_index_cache possible_names = [row[self._source_name_key]] for alias_column in self._source_name_alias: possible_names += row[alias_column].split(',') if not name in possible_names: self.__dict__.pop('_name_to_index_cache') index = self._name_to_index_cache[name] return index
[docs] def source_name(self, index): """Look up source name by row index. Parameters ---------- index : int Row index of source in table """ source_name_col = self.table[self._source_name_key] name = source_name_col[index] return name.strip()
def __getitem__(self, key): """Get source by name. Parameters ---------- key : str or int Source name or row index Returns ------- source : `SourceCatalogObject` An object representing one source. Notes ----- At the moment this can raise KeyError, IndexError and ValueError for invalid keys. Should we always raise KeyError to simplify this? """ if isinstance(key, six.string_types): index = self.row_index(key) elif _is_int(key): index = key else: msg = 'Key must be source name string or row index integer. ' msg += 'Type not understood: {}'.format(type(key)) raise ValueError(msg) return self._make_source_object(index) def _make_source_object(self, index): """Make one source object. Parameters ---------- index : int Row index Returns ------- source : `SourceCatalogObject` Source object """ data = self._make_source_dict(index) source = self.source_object_class(data) return source def _make_source_dict(self, idx): """Make one source data dict. Parameters ---------- idx : int Row index Returns ------- data : `~collections.OrderedDict` Source data """ data = OrderedDict() for colname in self.table.colnames: col = self.table[colname] if isinstance(col, Quantity): val = col[idx] else: val = col.data[idx] if col.unit: val = Quantity(val, col.unit) data[colname] = val data[self._source_index_key] = idx return data
[docs] def info(self): """Print info string.""" print(self)
def __str__(self): """Info string.""" ss = self.description ss += ' with {} objects.'.format(len(self.table)) return ss