channelindex.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. # -*- coding: utf-8 -*-
  2. '''
  3. This module defines :class:`ChannelIndex`, a container for multiple
  4. data channels.
  5. :class:`ChannelIndex` derives from :class:`Container`,
  6. from :module:`neo.core.container`.
  7. '''
  8. # needed for Python 3 compatibility
  9. from __future__ import absolute_import, division, print_function
  10. import numpy as np
  11. import quantities as pq
  12. from neo.core.container import Container
  13. class ChannelIndex(Container):
  14. '''
  15. A container for indexing/grouping data channels.
  16. This container has several purposes:
  17. * Grouping all :class:`AnalogSignal`\s and
  18. :class:`IrregularlySampledSignal`\s inside a :class:`Block` across
  19. :class:`Segment`\s;
  20. * Indexing a subset of the channels within an :class:`AnalogSignal` and
  21. :class:`IrregularlySampledSignal`\s;
  22. * Container of :class:`Unit`\s. Discharges of multiple neurons
  23. (:class:`Unit`\'s) can be seen on the same channel.
  24. *Usage 1* providing channel IDs across multiple :class:`Segment`::
  25. * Recording with 2 electrode arrays across 3 segments
  26. * Each array has 64 channels and is data is represented in a single
  27. :class:`AnalogSignal` object per electrode array
  28. * channel ids range from 0 to 127 with the first half covering
  29. electrode 0 and second half covering electrode 1
  30. >>> from neo.core import (Block, Segment, ChannelIndex,
  31. ... AnalogSignal)
  32. >>> from quantities import nA, kHz
  33. >>> import numpy as np
  34. ...
  35. >>> # create a Block with 3 Segment and 2 ChannelIndex objects
  36. >>> blk = Block()
  37. >>> for ind in range(3):
  38. ... seg = Segment(name='segment %d' % ind, index=ind)
  39. ... blk.segments.append(seg)
  40. ...
  41. >>> for ind in range(2):
  42. ... channel_ids=np.arange(64)+ind
  43. ... chx = ChannelIndex(name='Array probe %d' % ind,
  44. ... index=np.arange(64),
  45. ... channel_ids=channel_ids,
  46. ... channel_names=['Channel %i' % chid
  47. ... for chid in channel_ids])
  48. ... blk.channel_indexes.append(chx)
  49. ...
  50. >>> # Populate the Block with AnalogSignal objects
  51. >>> for seg in blk.segments:
  52. ... for chx in blk.channel_indexes:
  53. ... a = AnalogSignal(np.random.randn(10000, 64)*nA,
  54. ... sampling_rate=10*kHz)
  55. ... # link AnalogSignal and ID providing channel_index
  56. ... a.channel_index = chx
  57. ... chx.analogsignals.append(a)
  58. ... seg.analogsignals.append(a)
  59. *Usage 2* grouping channels::
  60. * Recording with a single probe with 8 channels, 4 of which belong to a
  61. Tetrode
  62. * Global channel IDs range from 0 to 8
  63. * An additional ChannelIndex is used to group subset of Tetrode channels
  64. >>> from neo.core import Block, ChannelIndex
  65. >>> import numpy as np
  66. >>> from quantities import mV, kHz
  67. ...
  68. >>> # Create a Block
  69. >>> blk = Block()
  70. >>> blk.segments.append(Segment())
  71. ...
  72. >>> # Create a signal with 8 channels and a ChannelIndex handling the
  73. >>> # channel IDs (see usage case 1)
  74. >>> sig = AnalogSignal(np.random.randn(1000, 8)*mV, sampling_rate=10*kHz)
  75. >>> chx = ChannelIndex(name='Probe 0', index=range(8),
  76. ... channel_ids=range(8),
  77. ... channel_names=['Channel %i' % chid
  78. ... for chid in range(8)])
  79. >>> chx.analogsignals.append(sig)
  80. >>> sig.channel_index=chx
  81. >>> blk.segments[0].analogsignals.append(sig)
  82. ...
  83. >>> # Create a new ChannelIndex which groups four channels from the
  84. >>> # analogsignal and provides a second ID scheme
  85. >>> chx = ChannelIndex(name='Tetrode 0',
  86. ... channel_names=np.array(['Tetrode ch1',
  87. ... 'Tetrode ch4',
  88. ... 'Tetrode ch6',
  89. ... 'Tetrode ch7']),
  90. ... index=np.array([0, 3, 5, 6]))
  91. >>> # Attach the ChannelIndex to the the Block,
  92. >>> # but not the to the AnalogSignal, since sig.channel_index is
  93. >>> # already linked to the global ChannelIndex of Probe 0 created above
  94. >>> chx.analogsignals.append(sig)
  95. >>> blk.channel_indexes.append(chx)
  96. *Usage 3* dealing with :class:`Unit` objects::
  97. * Group 5 unit objects in a single :class:`ChannelIndex` object
  98. >>> from neo.core import Block, ChannelIndex, Unit
  99. ...
  100. >>> # Create a Block
  101. >>> blk = Block()
  102. ...
  103. >>> # Create a new ChannelIndex and add it to the Block
  104. >>> chx = ChannelIndex(index=None, name='octotrode A')
  105. >>> blk.channel_indexes.append(chx)
  106. ...
  107. >>> # create several Unit objects and add them to the
  108. >>> # ChannelIndex
  109. >>> for ind in range(5):
  110. ... unit = Unit(name = 'unit %d' % ind,
  111. ... description='after a long and hard spike sorting')
  112. ... chx.units.append(unit)
  113. *Required attributes/properties*:
  114. :index: (numpy.array 1D dtype='i')
  115. Index of each channel in the attached signals (AnalogSignals and
  116. IrregularlySampledSignals). The order of the channel IDs needs to
  117. be consistent across attached signals.
  118. *Recommended attributes/properties*:
  119. :name: (str) A label for the dataset.
  120. :description: (str) Text description.
  121. :file_origin: (str) Filesystem path or URL of the original data file.
  122. :channel_names: (numpy.array 1D dtype='S')
  123. Names for each recording channel.
  124. :channel_ids: (numpy.array 1D dtype='int')
  125. IDs of the corresponding channels referenced by 'index'.
  126. :coordinates: (quantity array 2D (x, y, z))
  127. Physical or logical coordinates of all channels.
  128. Note: Any other additional arguments are assumed to be user-specific
  129. metadata and stored in :attr:`annotations`.
  130. *Container of*:
  131. :class:`AnalogSignal`
  132. :class:`IrregularlySampledSignal`
  133. :class:`Unit`
  134. '''
  135. _container_child_objects = ('Unit',)
  136. _data_child_objects = ('AnalogSignal', 'IrregularlySampledSignal')
  137. _single_parent_objects = ('Block',)
  138. _necessary_attrs = (('index', np.ndarray, 1, np.dtype('i')),)
  139. _recommended_attrs = ((('channel_names', np.ndarray, 1, np.dtype('S')),
  140. ('channel_ids', np.ndarray, 1, np.dtype('i')),
  141. ('coordinates', pq.Quantity, 2)) +
  142. Container._recommended_attrs)
  143. def __init__(self, index, channel_names=None, channel_ids=None,
  144. name=None, description=None, file_origin=None,
  145. coordinates=None, **annotations):
  146. '''
  147. Initialize a new :class:`ChannelIndex` instance.
  148. '''
  149. # Inherited initialization
  150. # Sets universally recommended attributes, and places all others
  151. # in annotations
  152. super(ChannelIndex, self).__init__(name=name,
  153. description=description,
  154. file_origin=file_origin,
  155. **annotations)
  156. # Defaults
  157. if channel_names is None:
  158. channel_names = np.array([], dtype='S')
  159. if channel_ids is None:
  160. channel_ids = np.array([], dtype='i')
  161. # Store recommended attributes
  162. self.channel_names = np.array(channel_names)
  163. self.channel_ids = np.array(channel_ids)
  164. self.index = np.array(index)
  165. self.coordinates = coordinates
  166. def __getitem__(self, i):
  167. '''
  168. Get the item or slice :attr:`i`.
  169. '''
  170. index = self.index.__getitem__(i)
  171. if self.channel_names.size > 0:
  172. channel_names = self.channel_names[index]
  173. if not channel_names.shape:
  174. channel_names = [channel_names]
  175. else:
  176. channel_names = None
  177. if self.channel_ids.size > 0:
  178. channel_ids = self.channel_ids[index]
  179. if not channel_ids.shape:
  180. channel_ids = [channel_ids]
  181. else:
  182. channel_ids = None
  183. obj = ChannelIndex(index=np.arange(index.size),
  184. channel_names=channel_names,
  185. channel_ids=channel_ids)
  186. obj.block = self.block
  187. obj.analogsignals = self.analogsignals
  188. obj.irregularlysampledsignals = self.irregularlysampledsignals
  189. # we do not copy the list of units, since these are related to
  190. # the entire set of channels in the parent ChannelIndex
  191. return obj