basefromrawio_new.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. """
  2. BaseFromRaw
  3. ======
  4. BaseFromRaw implement a bridge between the new neo.rawio API
  5. and the neo.io legacy that give neo.core object.
  6. The neo.rawio API is more restricted and limited and do not cover tricky
  7. cases with asymetrical tree of neo object.
  8. But if a format is done in neo.rawio the neo.io is done for free
  9. by inheritance of this class.
  10. Furthermore, IOs that inherits this BaseFromRaw also have the ability
  11. of the lazy load with proxy objects.
  12. """
  13. import collections
  14. import warnings
  15. import numpy as np
  16. from neo import logging_handler
  17. from neo.core import (AnalogSignal, Block,
  18. Epoch, Event,
  19. Group, ChannelView,
  20. Segment, SpikeTrain)
  21. from neo.io.baseio import BaseIO
  22. from neo.io.proxyobjects import (AnalogSignalProxy,
  23. SpikeTrainProxy, EventProxy, EpochProxy,
  24. ensure_signal_units, check_annotations,
  25. ensure_second, proxyobjectlist)
  26. import quantities as pq
  27. class BaseFromRaw(BaseIO):
  28. """
  29. This implement generic reader on top of RawIO reader.
  30. Arguments depend on `mode` (dir or file)
  31. File case::
  32. reader = BlackRockIO(filename='FileSpec2.3001.nev')
  33. Dir case::
  34. reader = NeuralynxIO(dirname='Cheetah_v5.7.4/original_data')
  35. Other arguments are IO specific.
  36. """
  37. is_readable = True
  38. is_writable = False
  39. supported_objects = [Block, Segment, AnalogSignal,
  40. SpikeTrain, Group, ChannelView, Event, Epoch]
  41. readable_objects = [Block, Segment]
  42. writeable_objects = []
  43. support_lazy = True
  44. name = 'BaseIO'
  45. description = ''
  46. extentions = []
  47. mode = 'file'
  48. _prefered_signal_group_mode = 'group-by-same-units' # 'split-all'
  49. _prefered_units_group_mode = 'all-in-one' # 'split-all'
  50. _default_group_mode_have_change_in_0_9 = False
  51. def __init__(self, *args, **kargs):
  52. BaseIO.__init__(self, *args, **kargs)
  53. self.parse_header()
  54. def read_block(self, block_index=0, lazy=False, signal_group_mode=None,
  55. units_group_mode=None, load_waveforms=False):
  56. """
  57. :param block_index: int default 0. In case of several block block_index can be specified.
  58. :param lazy: False by default.
  59. :param signal_group_mode: 'split-all' or 'group-by-same-units' (default depend IO):
  60. This control behavior for grouping channels in AnalogSignal.
  61. * 'split-all': each channel will give an AnalogSignal
  62. * 'group-by-same-units' all channel sharing the same quantity units ar grouped in
  63. a 2D AnalogSignal
  64. :param units_group_mode: 'split-all' or 'all-in-one'(default depend IO)
  65. This control behavior for grouping Unit in ChannelIndex:
  66. * 'split-all': each neo.Unit is assigned to a new neo.ChannelIndex
  67. * 'all-in-one': all neo.Unit are grouped in the same neo.ChannelIndex
  68. (global spike sorting for instance)
  69. :param load_waveforms: False by default. Control SpikeTrains.waveforms is None or not.
  70. """
  71. if signal_group_mode is None:
  72. signal_group_mode = self._prefered_signal_group_mode
  73. if self._default_group_mode_have_change_in_0_9:
  74. warnings.warn('default "signal_group_mode" have change in version 0.9:'
  75. 'now all channels are group together in AnalogSignal')
  76. if units_group_mode is None:
  77. units_group_mode = self._prefered_units_group_mode
  78. # annotations
  79. bl_annotations = dict(self.raw_annotations['blocks'][block_index])
  80. bl_annotations.pop('segments')
  81. bl_annotations = check_annotations(bl_annotations)
  82. bl = Block(**bl_annotations)
  83. # ChannelIndex and Unit
  84. # 2 case are possible in neo defifferent IO have choosen one or other:
  85. # * All units are grouped in the same ChannelIndex and indexes are all channels:
  86. # 'all-in-one'
  87. # * Each units is assigned to one ChannelIndex: 'split-all'
  88. # This is kept for compatibility
  89. unit_channels = self.header['unit_channels']
  90. if units_group_mode == 'all-in-one':
  91. if unit_channels.size > 0:
  92. channel_index = ChannelIndex(index=np.array([], dtype='i'),
  93. name='ChannelIndex for all Unit')
  94. bl.channel_indexes.append(channel_index)
  95. for c in range(unit_channels.size):
  96. unit_annotations = self.raw_annotations['unit_channels'][c]
  97. unit_annotations = check_annotations(unit_annotations)
  98. unit = Unit(**unit_annotations)
  99. channel_index.units.append(unit)
  100. elif units_group_mode == 'split-all':
  101. for c in range(len(unit_channels)):
  102. unit_annotations = self.raw_annotations['unit_channels'][c]
  103. unit_annotations = check_annotations(unit_annotations)
  104. unit = Unit(**unit_annotations)
  105. channel_index = ChannelIndex(index=np.array([], dtype='i'),
  106. name='ChannelIndex for Unit')
  107. channel_index.units.append(unit)
  108. bl.channel_indexes.append(channel_index)
  109. # Read all segments
  110. for seg_index in range(self.segment_count(block_index)):
  111. seg = self.read_segment(block_index=block_index, seg_index=seg_index,
  112. lazy=lazy, signal_group_mode=signal_group_mode,
  113. load_waveforms=load_waveforms)
  114. bl.segments.append(seg)
  115. # create link to other containers ChannelIndex and Units
  116. for seg in bl.segments:
  117. for c, anasig in enumerate(seg.analogsignals):
  118. bl.channel_indexes[c].analogsignals.append(anasig)
  119. nsig = len(seg.analogsignals)
  120. for c, sptr in enumerate(seg.spiketrains):
  121. if units_group_mode == 'all-in-one':
  122. bl.channel_indexes[nsig].units[c].spiketrains.append(sptr)
  123. elif units_group_mode == 'split-all':
  124. bl.channel_indexes[nsig + c].units[0].spiketrains.append(sptr)
  125. bl.create_many_to_one_relationship()
  126. return bl
  127. def read_segment(self, block_index=0, seg_index=0, lazy=False,
  128. signal_group_mode=None, load_waveforms=False, time_slice=None,
  129. strict_slicing=True):
  130. """
  131. :param block_index: int default 0. In case of several blocks block_index can be specified.
  132. :param seg_index: int default 0. Index of segment.
  133. :param lazy: False by default.
  134. :param signal_group_mode: 'split-all' or 'group-by-same-units' (default depend IO):
  135. This control behavior for grouping channels in AnalogSignal.
  136. * 'split-all': each channel will give an AnalogSignal
  137. * 'group-by-same-units' all channel sharing the same quantity units ar grouped in
  138. a 2D AnalogSignal
  139. :param load_waveforms: False by default. Control SpikeTrains.waveforms is None or not.
  140. :param time_slice: None by default means no limit.
  141. A time slice is (t_start, t_stop) both are quantities.
  142. All object AnalogSignal, SpikeTrain, Event, Epoch will load only in the slice.
  143. :param strict_slicing: True by default.
  144. Control if an error is raised or not when t_start or t_stop
  145. is outside the real time range of the segment.
  146. """
  147. if lazy:
  148. assert time_slice is None,\
  149. 'For lazy=True you must specify time_slice when LazyObject.load(time_slice=...)'
  150. assert not load_waveforms,\
  151. 'For lazy=True you must specify load_waveforms when SpikeTrain.load(load_waveforms=...)'
  152. if signal_group_mode is None:
  153. signal_group_mode = self._prefered_signal_group_mode
  154. # annotations
  155. seg_annotations = dict(self.raw_annotations['blocks'][block_index]['segments'][seg_index])
  156. for k in ('signals', 'units', 'events'):
  157. seg_annotations.pop(k)
  158. seg_annotations = check_annotations(seg_annotations)
  159. seg = Segment(index=seg_index, **seg_annotations)
  160. # AnalogSignal
  161. signal_channels = self.header['signal_channels']
  162. if signal_channels.size > 0:
  163. channel_index_list = self.get_group_signal_channel_indexes()
  164. for channel_index in channel_index_list:
  165. for ind_within in self._make_signal_channel_subgroups(
  166. channel_index, signal_group_mode):
  167. ind_abs = channel_index[ind_within]
  168. # make a proxy...
  169. anasig = AnalogSignalProxy(rawio=self, global_channel_indexes=ind_abs,
  170. block_index=block_index, seg_index=seg_index)
  171. if not lazy:
  172. # ... and get the real AnalogSIgnal if not lazy
  173. anasig = anasig.load(time_slice=time_slice, strict_slicing=strict_slicing)
  174. # TODO magnitude_mode='rescaled'/'raw'
  175. anasig.segment = seg
  176. seg.analogsignals.append(anasig)
  177. # SpikeTrain and waveforms (optional)
  178. unit_channels = self.header['unit_channels']
  179. for unit_index in range(len(unit_channels)):
  180. # make a proxy...
  181. sptr = SpikeTrainProxy(rawio=self, unit_index=unit_index,
  182. block_index=block_index, seg_index=seg_index)
  183. if not lazy:
  184. # ... and get the real SpikeTrain if not lazy
  185. sptr = sptr.load(time_slice=time_slice, strict_slicing=strict_slicing,
  186. load_waveforms=load_waveforms)
  187. # TODO magnitude_mode='rescaled'/'raw'
  188. sptr.segment = seg
  189. seg.spiketrains.append(sptr)
  190. # Events/Epoch
  191. event_channels = self.header['event_channels']
  192. for chan_ind in range(len(event_channels)):
  193. if event_channels['type'][chan_ind] == b'event':
  194. e = EventProxy(rawio=self, event_channel_index=chan_ind,
  195. block_index=block_index, seg_index=seg_index)
  196. if not lazy:
  197. e = e.load(time_slice=time_slice, strict_slicing=strict_slicing)
  198. e.segment = seg
  199. seg.events.append(e)
  200. elif event_channels['type'][chan_ind] == b'epoch':
  201. e = EpochProxy(rawio=self, event_channel_index=chan_ind,
  202. block_index=block_index, seg_index=seg_index)
  203. if not lazy:
  204. e = e.load(time_slice=time_slice, strict_slicing=strict_slicing)
  205. e.segment = seg
  206. seg.epochs.append(e)
  207. seg.create_many_to_one_relationship()
  208. return seg
  209. def _make_signal_channel_subgroups(self, channel_index,
  210. signal_group_mode='group-by-same-units'):
  211. """
  212. For some RawIOs channels are already split into groups,
  213. but in any case, channels need to be split again into subgroups
  214. because they do not have the same physical units (e.g. volts vs amps)
  215. They can also be split one by one to match the previous behaviour for
  216. some IOs in older versions of Neo (<=0.5).
  217. This method returns indices that can be used to either (i) aggregate signal
  218. channels with the same physical units or (ii) split all channels.
  219. """
  220. all_channels = self.header['signal_channels']
  221. if channel_index is None:
  222. channel_index = np.arange(all_channels.size, dtype=int)
  223. channels = all_channels[channel_index]
  224. index_groups = []
  225. if signal_group_mode == 'group-by-same-units':
  226. all_units = np.unique(channels['units'])
  227. for unit in all_units:
  228. ind_within, = np.nonzero(channels['units'] == unit)
  229. index_groups.append(ind_within)
  230. elif signal_group_mode == 'split-all':
  231. for i, chan_index in enumerate(channel_index):
  232. ind_within = [i]
  233. index_groups.append(ind_within)
  234. else:
  235. raise NotImplementedError()
  236. return index_groups