""" 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.utils.decorators import deprecated
from pypet.pypetlogging import HasLogger
import pypet.compat as compat
[docs]class Annotations(object):
""" 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: http://pytables.github.io/usersguide/libref/declarative_classes.html#the-attributeset-class
"""
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`.
"""
if isinstance(key, int):
if key == 0:
key = 'annotation'
else:
key = 'annotation_%d' % key
setattr(self, key, value)
[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 annotiations from RAM """
self.__dict__ = {}
[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__[compat.listkeys(self.__dict__)[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(compat.listkeys(self.__dict__))))
else:
raise AttributeError('Your annotation is empty, cannot access data.')
result_list = []
for name in args:
if isinstance(name, int):
if name == 0:
name = 'annotation'
else:
name = 'annotation_%d' % name
result_list.append(getattr(self, 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):
if idx == 0:
valstr = 'annotation'
else:
valstr = 'annotation_' + str(idx)
self.f_set_single(valstr, arg)
for key, arg in kwargs.items():
self.f_set_single(key, arg)
[docs] def f_set_single(self, name, data):
""" Sets a single annotation. """
setattr(self, 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):
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: http://pytables.github.io/usersguide/libref/declarative_classes.html#the-attributeset-class
"""
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()
@deprecated('Please use `f_ann_to_str.')
def f_ann_to_string(self):
"""Returns annotations as string
Equivalent to `v_annotations.f_ann_to_str()`
DEPRECATED: Please use `f_ann_to_str()` instead.
"""
return self._annotations.f_ann_to_str()