Source code for pypet.annotations

""" Module defining annotations.

This module contains two classes:

    1. :class:`~pypet.annotations.Annotations`

        Container class for annotations. There are no restrictions regarding what is considered
        to be an annotation. In principle, this can be any python object. However,
        if using the standard :class:`~pypet.storageservice.HDF5StorageService`,
        these annotations are stored as hdf5 node attributes.
        Accordingly, annotations should be rather small since reading
        and writing these attributes to disk is slow.

    2. :class:`~pypet.annotations.WithAnnotations`

        Abstract class to subclass from. Instances that subclass `WithAnnotations`
        have the `v_annotations` property which is an instance of the `Annotations` class
        to handle annotations. They also acquire some functions to put annotations directly
        into the `v_annotations` object like `f_set_annotations`.


__author__ = 'Robert Meyer'

from pypet.pypetlogging import HasLogger
from pypet.slots import HasSlots

[docs]class Annotations(HasSlots): """ Simple container class for annotations. Every tree node (*leaves* and *group* nodes) can be annotated. In case you use the standard :class:`~pypet.storageservice.HDF5StorageService`, these annotations are stored in the attributes of the hdf5 nodes in the hdf5 file, you might wanna take a look at pytables attributes_. Annotations should be small (short strings or basic python data types) since their storage and retrieval is quite slow! .. _attributes: """ __slots__ = ('_dict_',) def __init__(self): self._dict_ = None @property def _dict(self): if self._dict_ is None: self._dict_ = {} return self._dict_ def __iter__(self): return self._dict.__iter__() def __getitem__(self, item): """Equivalent to calling f_get()""" return self.f_get(item) def __setitem__(self, key, value): """Almost equivalent to calling __setattr__. Treats integer values as `f_get`. """ self.f_set(**{key: value}) def __delitem__(self, key): self.f_remove(key)
[docs] def f_to_dict(self, copy=True): """Returns annotations as dictionary. :param copy: Whether to return a shallow copy or the real thing (aka _dict). """ if copy: return self._dict.copy() else: return self._dict
[docs] def f_is_empty(self): """Checks if annotations are empty""" return len(self._dict) == 0
[docs] def f_empty(self): """Removes all annotations from RAM """ self._dict_ = None
def __setattr__(self, key, value): if key.startswith('_'): # We set a private attribute super(Annotations, self).__setattr__(key, value) else: self.f_set_single(key, value) def __getattr__(self, item): return self.f_get(item) def __delattr__(self, item): self.f_remove(item) def _translate_key(self, key): if isinstance(key, int): if key == 0: key = 'annotation' else: key = 'annotation_%d' % key return key
[docs] def f_get(self, *args): """Returns annotations If len(args)>1, then returns a list of annotations. `f_get(X)` with *X* integer will return the annotation with name `annotation_X`. If the annotation contains only a single entry you can call `f_get()` without arguments. If you call `f_get()` and the annotation contains more than one element a ValueError is thrown. """ if len(args) == 0: if len(self._dict) == 1: return self._dict[list(self._dict.keys())[0]] elif len(self._dict) > 1: raise ValueError('Your annotation contains more than one entry: ' '`%s` Please use >>f_get<< with one of these.' % (str(list(self._dict.keys())))) else: raise AttributeError('Your annotation is empty, cannot access data.') result_list = [] for name in args: name = self._translate_key(name) try: result_list.append(self._dict[name]) except KeyError: raise AttributeError('Your annotation does not contain %s.' % name) if len(args) == 1: return result_list[0] else: return tuple(result_list)
[docs] def f_set(self, *args, **kwargs): """Sets annotations Items in args are added as `annotation` and `annotation_X` where 'X' is the position in args for following arguments. """ for idx, arg in enumerate(args): valstr = self._translate_key(idx) self.f_set_single(valstr, arg) for key, arg in kwargs.items(): self.f_set_single(key, arg)
[docs] def f_remove(self, key): """Removes `key` from annotations""" key = self._translate_key(key) try: del self._dict[key] except KeyError: raise AttributeError('Your annotations do not contain %s' % key)
[docs] def f_set_single(self, name, data): """ Sets a single annotation. """ self._dict[name] = data
[docs] def f_ann_to_str(self): """Returns all annotations lexicographically sorted as a concatenated string.""" resstr = '' for key in sorted(self._dict.keys()): resstr += '%s=%s; ' % (key, str(self._dict[key])) return resstr[:-2]
def __str__(self): return self.f_ann_to_str()
class WithAnnotations(HasLogger): __slots__ = ('_annotations',) def __init__(self): self._annotations = Annotations() # The annotation object to handle annotations @property def v_annotations(self): """ Annotation feature of a trajectory node. Store some short additional information about your nodes here. If you use the standard HDF5 storage service, they will be stored as hdf5 node attributes_. .. _attributes: """ return self._annotations def f_set_annotations(self, *args, **kwargs): """Sets annotations Equivalent to calling `v_annotations.f_set(*args,**kwargs)` """ self._annotations.f_set(*args, **kwargs) def f_get_annotations(self, *args): """Returns annotations Equivalent to `v_annotations.f_get(*args)` """ return self._annotations.f_get(*args) def f_ann_to_str(self): """Returns annotations as string Equivalent to `v_annotations.f_ann_to_str()` """ return self._annotations.f_ann_to_str()