baseneo.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. """
  2. This module defines :class:`BaseNeo`, the abstract base class
  3. used by all :module:`neo.core` classes.
  4. """
  5. from copy import deepcopy
  6. from datetime import datetime, date, time, timedelta
  7. from decimal import Decimal
  8. import logging
  9. from numbers import Number
  10. import numpy as np
  11. ALLOWED_ANNOTATION_TYPES = (int, float, complex,
  12. str, bytes,
  13. type(None),
  14. datetime, date, time, timedelta,
  15. Number, Decimal,
  16. np.number, np.bool_)
  17. # handle both Python 2 and Python 3
  18. try:
  19. ALLOWED_ANNOTATION_TYPES += (long, unicode)
  20. except NameError:
  21. pass
  22. logger = logging.getLogger("Neo")
  23. class MergeError(Exception):
  24. pass
  25. def _check_annotations(value):
  26. """
  27. Recursively check that value is either of a "simple" type (number, string,
  28. date/time) or is a (possibly nested) dict, list or numpy array containing
  29. only simple types.
  30. """
  31. if isinstance(value, np.ndarray):
  32. if not issubclass(value.dtype.type, ALLOWED_ANNOTATION_TYPES):
  33. raise ValueError("Invalid annotation. NumPy arrays with dtype %s"
  34. "are not allowed" % value.dtype.type)
  35. elif isinstance(value, dict):
  36. for element in value.values():
  37. _check_annotations(element)
  38. elif isinstance(value, (list, tuple)):
  39. for element in value:
  40. _check_annotations(element)
  41. elif not isinstance(value, ALLOWED_ANNOTATION_TYPES):
  42. raise ValueError("Invalid annotation. Annotations of type %s are not"
  43. "allowed" % type(value))
  44. def merge_annotation(a, b):
  45. """
  46. First attempt at a policy for merging annotations (intended for use with
  47. parallel computations using MPI). This policy needs to be discussed
  48. further, or we could allow the user to specify a policy.
  49. Current policy:
  50. For arrays or lists: concatenate
  51. For dicts: merge recursively
  52. For strings: concatenate with ';'
  53. Otherwise: fail if the annotations are not equal
  54. """
  55. assert type(a) == type(b), 'type({}) {} != type({}) {}'.format(a, type(a),
  56. b, type(b))
  57. if isinstance(a, dict):
  58. return merge_annotations(a, b)
  59. elif isinstance(a, np.ndarray): # concatenate b to a
  60. return np.append(a, b)
  61. elif isinstance(a, list): # concatenate b to a
  62. return a + b
  63. elif isinstance(a, str):
  64. if a == b:
  65. return a
  66. else:
  67. return a + ";" + b
  68. else:
  69. assert a == b, '{} != {}'.format(a, b)
  70. return a
  71. def merge_annotations(A, *Bs):
  72. """
  73. Merge two sets of annotations.
  74. Merging follows these rules:
  75. All keys that are in A or B, but not both, are kept.
  76. For keys that are present in both:
  77. For arrays or lists: concatenate
  78. For dicts: merge recursively
  79. For strings: concatenate with ';'
  80. Otherwise: warn if the annotations are not equal
  81. """
  82. merged = A.copy()
  83. for B in Bs:
  84. for name in B:
  85. if name not in merged:
  86. merged[name] = B[name]
  87. else:
  88. try:
  89. merged[name] = merge_annotation(merged[name], B[name])
  90. except BaseException as exc:
  91. # exc.args += ('key %s' % name,)
  92. # raise
  93. merged[name] = "MERGE CONFLICT" # temporary hack
  94. logger.debug("Merging annotations: A=%s Bs=%s merged=%s", A, Bs, merged)
  95. return merged
  96. def intersect_annotations(A, B):
  97. """
  98. Identify common entries in dictionaries A and B
  99. and return these in a separate dictionary.
  100. Entries have to share key as well as value to be
  101. considered common.
  102. Parameters
  103. ----------
  104. A, B : dict
  105. Dictionaries to merge.
  106. """
  107. result = {}
  108. for key in set(A.keys()) & set(B.keys()):
  109. v1, v2 = A[key], B[key]
  110. assert type(v1) == type(v2), 'type({}) {} != type({}) {}'.format(v1, type(v1),
  111. v2, type(v2))
  112. if isinstance(v1, dict) and v1 == v2:
  113. result[key] = deepcopy(v1)
  114. elif isinstance(v1, str) and v1 == v2:
  115. result[key] = A[key]
  116. elif isinstance(v1, list) and v1 == v2:
  117. result[key] = deepcopy(v1)
  118. elif isinstance(v1, np.ndarray) and all(v1 == v2):
  119. result[key] = deepcopy(v1)
  120. return result
  121. def _reference_name(class_name):
  122. """
  123. Given the name of a class, return an attribute name to be used for
  124. references to instances of that class.
  125. For example, a Segment object has a parent Block object, referenced by
  126. `segment.block`. The attribute name `block` is obtained by calling
  127. `_container_name("Block")`.
  128. """
  129. name_map = {
  130. "ChannelIndex": "channel_index"
  131. }
  132. return name_map.get(class_name, class_name.lower())
  133. def _container_name(class_name):
  134. """
  135. Given the name of a class, return an attribute name to be used for
  136. lists (or other containers) containing instances of that class.
  137. For example, a Block object contains a list of Segment objects,
  138. referenced by `block.segments`. The attribute name `segments` is
  139. obtained by calling `_container_name_plural("Segment")`.
  140. """
  141. name_map = {
  142. "ChannelIndex": "channel_indexes"
  143. }
  144. return name_map.get(class_name, _reference_name(class_name) + 's')
  145. class BaseNeo:
  146. """
  147. This is the base class from which all Neo objects inherit.
  148. This class implements support for universally recommended arguments,
  149. and also sets up the :attr:`annotations` dict for additional arguments.
  150. Each class can define one or more of the following class attributes:
  151. :_single_parent_objects: Neo objects that can be parents of this
  152. object. This attribute is used in cases where
  153. only one parent of this class is allowed.
  154. An instance attribute named
  155. class.__name__.lower() will be automatically
  156. defined to hold this parent and will be
  157. initialized to None.
  158. :_multi_parent_objects: Neo objects that can be parents of this
  159. object. This attribute is used in cases where
  160. multiple parents of this class is allowed.
  161. An instance attribute named
  162. class.__name__.lower()+'s' will be
  163. automatically defined to hold this parent and
  164. will be initialized to an empty list.
  165. :_necessary_attrs: A list of tuples containing the attributes that the
  166. class must have. The tuple can have 2-4 elements.
  167. The first element is the attribute name.
  168. The second element is the attribute type.
  169. The third element is the number of dimensions
  170. (only for numpy arrays and quantities).
  171. The fourth element is the dtype of array
  172. (only for numpy arrays and quantities).
  173. This does NOT include the attributes holding the
  174. parents or children of the object.
  175. :_recommended_attrs: A list of tuples containing the attributes that
  176. the class may optionally have. It uses the same
  177. structure as :_necessary_attrs:
  178. :_repr_pretty_attrs_keys_: The names of attributes printed when
  179. pretty-printing using iPython.
  180. The following helper properties are available:
  181. :_parent_objects: All parent objects.
  182. :_single_parent_objects: + :_multi_parent_objects:
  183. :_single_parent_containers: The names of the container attributes used
  184. to store :_single_parent_objects:
  185. :_multi_parent_containers: The names of the container attributes used
  186. to store :_multi_parent_objects:
  187. :_parent_containers: All parent container attributes.
  188. :_single_parent_containers: +
  189. :_multi_parent_containers:
  190. :parents: All objects that are parents of the current object.
  191. :_all_attrs: All required and optional attributes.
  192. :_necessary_attrs: + :_recommended_attrs:
  193. The following "universal" methods are available:
  194. :__init__: Grabs the universally recommended arguments :attr:`name`,
  195. :attr:`file_origin`, and :attr:`description` and stores them as
  196. attributes.
  197. Also takes every additional argument (that is, every argument
  198. that is not handled by :class:`BaseNeo` or the child class), and
  199. puts in the dict :attr:`annotations`.
  200. :annotate(**args): Updates :attr:`annotations` with keyword/value
  201. pairs.
  202. :merge(**args): Merge the contents of another object into this one.
  203. The merge method implemented here only merges
  204. annotations (see :merge_annotations:).
  205. Subclasses should implementt their own merge rules.
  206. :merge_annotations(**args): Merge the :attr:`annotations` of another
  207. object into this one.
  208. Each child class should:
  209. 0) describe its parents (if any) and attributes in the relevant
  210. class attributes. :_recommended_attrs: should append
  211. BaseNeo._recommended_attrs to the end.
  212. 1) call BaseNeo.__init__(self, name=name, description=description,
  213. file_origin=file_origin, **annotations)
  214. with the universal recommended arguments, plus optional annotations
  215. 2) process its required arguments in its __new__ or __init__ method
  216. 3) process its non-universal recommended arguments (in its __new__ or
  217. __init__ method
  218. Non-keyword arguments should only be used for required arguments.
  219. The required and recommended arguments for each child class (Neo object)
  220. are specified in the _necessary_attrs and _recommended_attrs attributes and
  221. documentation for the child object.
  222. """
  223. # these attributes control relationships, they need to be
  224. # specified in each child class
  225. # Parent objects whose children can have a single parent
  226. _single_parent_objects = ()
  227. # Attribute names corresponding to _single_parent_objects
  228. _single_parent_attrs = ()
  229. # Parent objects whose children can have multiple parents
  230. _multi_parent_objects = ()
  231. # Attributes that an instance is required to have defined
  232. _necessary_attrs = ()
  233. # Attributes that an instance may or may have defined
  234. _recommended_attrs = (('name', str),
  235. ('description', str),
  236. ('file_origin', str))
  237. # Attributes that are used for pretty-printing
  238. _repr_pretty_attrs_keys_ = ("name", "description", "annotations")
  239. def __init__(self, name=None, description=None, file_origin=None,
  240. **annotations):
  241. """
  242. This is the base constructor for all Neo objects.
  243. Stores universally recommended attributes and creates
  244. :attr:`annotations` from additional arguments not processed by
  245. :class:`BaseNeo` or the child class.
  246. """
  247. # create `annotations` for additional arguments
  248. _check_annotations(annotations)
  249. self.annotations = annotations
  250. # these attributes are recommended for all objects.
  251. self.name = name
  252. self.description = description
  253. self.file_origin = file_origin
  254. # initialize parent containers
  255. for parent in self._single_parent_containers:
  256. setattr(self, parent, None)
  257. for parent in self._multi_parent_containers:
  258. setattr(self, parent, [])
  259. def annotate(self, **annotations):
  260. """
  261. Add annotations (non-standardized metadata) to a Neo object.
  262. Example:
  263. >>> obj.annotate(key1=value0, key2=value1)
  264. >>> obj.key2
  265. value2
  266. """
  267. _check_annotations(annotations)
  268. self.annotations.update(annotations)
  269. def _has_repr_pretty_attrs_(self):
  270. return any(getattr(self, k) for k in self._repr_pretty_attrs_keys_)
  271. def _repr_pretty_attrs_(self, pp, cycle):
  272. first = True
  273. for key in self._repr_pretty_attrs_keys_:
  274. value = getattr(self, key)
  275. if value:
  276. if first:
  277. first = False
  278. else:
  279. pp.breakable()
  280. with pp.group(indent=1):
  281. pp.text("{}: ".format(key))
  282. pp.pretty(value)
  283. def _repr_pretty_(self, pp, cycle):
  284. """
  285. Handle pretty-printing the :class:`BaseNeo`.
  286. """
  287. pp.text(self.__class__.__name__)
  288. if self._has_repr_pretty_attrs_():
  289. pp.breakable()
  290. self._repr_pretty_attrs_(pp, cycle)
  291. @property
  292. def _single_parent_containers(self):
  293. """
  294. Containers for parent objects whose children can have a single parent.
  295. """
  296. return tuple([_reference_name(parent) for parent in
  297. self._single_parent_objects])
  298. @property
  299. def _multi_parent_containers(self):
  300. """
  301. Containers for parent objects whose children can have multiple parents.
  302. """
  303. return tuple([_container_name(parent) for parent in
  304. self._multi_parent_objects])
  305. @property
  306. def _parent_objects(self):
  307. """
  308. All types for parent objects.
  309. """
  310. return self._single_parent_objects + self._multi_parent_objects
  311. @property
  312. def _parent_containers(self):
  313. """
  314. All containers for parent objects.
  315. """
  316. return self._single_parent_containers + self._multi_parent_containers
  317. @property
  318. def parents(self):
  319. """
  320. All parent objects storing the current object.
  321. """
  322. single = [getattr(self, attr) for attr in
  323. self._single_parent_containers]
  324. multi = [list(getattr(self, attr)) for attr in
  325. self._multi_parent_containers]
  326. return tuple(single + sum(multi, []))
  327. @property
  328. def _all_attrs(self):
  329. """
  330. Returns a combination of all required and recommended
  331. attributes.
  332. """
  333. return self._necessary_attrs + self._recommended_attrs
  334. def merge_annotations(self, *others):
  335. """
  336. Merge annotations from the other object into this one.
  337. Merging follows these rules:
  338. All keys that are in the either object, but not both, are kept.
  339. For keys that are present in both objects:
  340. For arrays or lists: concatenate the two arrays
  341. For dicts: merge recursively
  342. For strings: concatenate with ';'
  343. Otherwise: fail if the annotations are not equal
  344. """
  345. other_annotations = [other.annotations for other in others]
  346. merged_annotations = merge_annotations(self.annotations,
  347. *other_annotations)
  348. self.annotations.update(merged_annotations)
  349. def merge(self, *others):
  350. """
  351. Merge the contents of another object into this one.
  352. See :meth:`merge_annotations` for details of the merge operation.
  353. """
  354. self.merge_annotations(*others)
  355. def set_parent(self, obj):
  356. """
  357. Set the appropriate "parent" attribute of this object
  358. according to the type of "obj"
  359. """
  360. if obj.__class__.__name__ not in self._single_parent_objects:
  361. raise TypeError("{} can only have parents of type {}, not {}".format(
  362. self.__class__.__name__, self._single_parent_objects, obj.__class__.__name__))
  363. loc = self._single_parent_objects.index(obj.__class__.__name__)
  364. parent_attr = self._single_parent_attrs[loc]
  365. setattr(self, parent_attr, obj)