segment.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. '''
  2. This module defines :class:`Segment`, a container for data sharing a common
  3. time basis.
  4. :class:`Segment` derives from :class:`Container`,
  5. from :module:`neo.core.container`.
  6. '''
  7. from datetime import datetime
  8. import numpy as np
  9. from copy import deepcopy
  10. from neo.core.container import Container
  11. class Segment(Container):
  12. '''
  13. A container for data sharing a common time basis.
  14. A :class:`Segment` is a heterogeneous container for discrete or continous
  15. data sharing a common clock (time basis) but not necessary the same
  16. sampling rate, start or end time.
  17. *Usage*::
  18. >>> from neo.core import Segment, SpikeTrain, AnalogSignal
  19. >>> from quantities import Hz, s
  20. >>>
  21. >>> seg = Segment(index=5)
  22. >>>
  23. >>> train0 = SpikeTrain(times=[.01, 3.3, 9.3], units='sec', t_stop=10)
  24. >>> seg.spiketrains.append(train0)
  25. >>>
  26. >>> train1 = SpikeTrain(times=[100.01, 103.3, 109.3], units='sec',
  27. ... t_stop=110)
  28. >>> seg.spiketrains.append(train1)
  29. >>>
  30. >>> sig0 = AnalogSignal(signal=[.01, 3.3, 9.3], units='uV',
  31. ... sampling_rate=1*Hz)
  32. >>> seg.analogsignals.append(sig0)
  33. >>>
  34. >>> sig1 = AnalogSignal(signal=[100.01, 103.3, 109.3], units='nA',
  35. ... sampling_period=.1*s)
  36. >>> seg.analogsignals.append(sig1)
  37. *Required attributes/properties*:
  38. None
  39. *Recommended attributes/properties*:
  40. :name: (str) A label for the dataset.
  41. :description: (str) Text description.
  42. :file_origin: (str) Filesystem path or URL of the original data file.
  43. :file_datetime: (datetime) The creation date and time of the original
  44. data file.
  45. :rec_datetime: (datetime) The date and time of the original recording
  46. :index: (int) You can use this to define a temporal ordering of
  47. your Segment. For instance you could use this for trial numbers.
  48. Note: Any other additional arguments are assumed to be user-specific
  49. metadata and stored in :attr:`annotations`.
  50. *Properties available on this object*:
  51. :all_data: (list) A list of all child objects in the :class:`Segment`.
  52. *Container of*:
  53. :class:`Epoch`
  54. :class:`Event`
  55. :class:`AnalogSignal`
  56. :class:`IrregularlySampledSignal`
  57. :class:`SpikeTrain`
  58. '''
  59. _data_child_objects = ('AnalogSignal',
  60. 'Epoch', 'Event',
  61. 'IrregularlySampledSignal', 'SpikeTrain', 'ImageSequence')
  62. _single_parent_objects = ('Block',)
  63. _recommended_attrs = ((('file_datetime', datetime),
  64. ('rec_datetime', datetime),
  65. ('index', int)) +
  66. Container._recommended_attrs)
  67. _repr_pretty_containers = ('analogsignals',)
  68. def __init__(self, name=None, description=None, file_origin=None,
  69. file_datetime=None, rec_datetime=None, index=None,
  70. **annotations):
  71. '''
  72. Initialize a new :class:`Segment` instance.
  73. '''
  74. super().__init__(name=name, description=description,
  75. file_origin=file_origin, **annotations)
  76. self.file_datetime = file_datetime
  77. self.rec_datetime = rec_datetime
  78. self.index = index
  79. # t_start attribute is handled as a property so type checking can be done
  80. @property
  81. def t_start(self):
  82. '''
  83. Time when first signal begins.
  84. '''
  85. t_starts = [sig.t_start for sig in self.analogsignals +
  86. self.spiketrains + self.irregularlysampledsignals]
  87. for e in self.epochs + self.events:
  88. if hasattr(e, 't_start'): # in case of proxy objects
  89. t_starts += [e.t_start]
  90. elif len(e) > 0:
  91. t_starts += [e.times[0]]
  92. # t_start is not defined if no children are present
  93. if len(t_starts) == 0:
  94. return None
  95. t_start = min(t_starts)
  96. return t_start
  97. # t_stop attribute is handled as a property so type checking can be done
  98. @property
  99. def t_stop(self):
  100. '''
  101. Time when last signal ends.
  102. '''
  103. t_stops = [sig.t_stop for sig in self.analogsignals +
  104. self.spiketrains + self.irregularlysampledsignals]
  105. for e in self.epochs + self.events:
  106. if hasattr(e, 't_stop'): # in case of proxy objects
  107. t_stops += [e.t_stop]
  108. elif len(e) > 0:
  109. t_stops += [e.times[-1]]
  110. # t_stop is not defined if no children are present
  111. if len(t_stops) == 0:
  112. return None
  113. t_stop = max(t_stops)
  114. return t_stop
  115. def take_spiketrains_by_unit(self, unit_list=None):
  116. '''
  117. Return :class:`SpikeTrains` in the :class:`Segment` that are also in a
  118. :class:`Unit` in the :attr:`unit_list` provided.
  119. '''
  120. if unit_list is None:
  121. return []
  122. spiketrain_list = []
  123. for spiketrain in self.spiketrains:
  124. if spiketrain.unit in unit_list:
  125. spiketrain_list.append(spiketrain)
  126. return spiketrain_list
  127. # def take_analogsignal_by_unit(self, unit_list=None):
  128. # '''
  129. # Return :class:`AnalogSignal` objects in the :class:`Segment` that are
  130. # have the same :attr:`channel_index` as any of the :class:`Unit: objects
  131. # in the :attr:`unit_list` provided.
  132. # '''
  133. # if unit_list is None:
  134. # return []
  135. # channel_indexes = []
  136. # for unit in unit_list:
  137. # if unit.channel_indexes is not None:
  138. # channel_indexes.extend(unit.channel_indexes)
  139. # return self.take_analogsignal_by_channelindex(channel_indexes)
  140. #
  141. # def take_analogsignal_by_channelindex(self, channel_indexes=None):
  142. # '''
  143. # Return :class:`AnalogSignal` objects in the :class:`Segment` that have
  144. # a :attr:`channel_index` that is in the :attr:`channel_indexes`
  145. # provided.
  146. # '''
  147. # if channel_indexes is None:
  148. # return []
  149. # anasig_list = []
  150. # for anasig in self.analogsignals:
  151. # if anasig.channel_index in channel_indexes:
  152. # anasig_list.append(anasig)
  153. # return anasig_list
  154. def take_slice_of_analogsignalarray_by_unit(self, unit_list=None):
  155. '''
  156. Return slices of the :class:`AnalogSignal` objects in the
  157. :class:`Segment` that correspond to a :attr:`channel_index` of any of
  158. the :class:`Unit` objects in the :attr:`unit_list` provided.
  159. '''
  160. if unit_list is None:
  161. return []
  162. indexes = []
  163. for unit in unit_list:
  164. if unit.get_channel_indexes() is not None:
  165. indexes.extend(unit.get_channel_indexes())
  166. return self.take_slice_of_analogsignalarray_by_channelindex(indexes)
  167. def take_slice_of_analogsignalarray_by_channelindex(self,
  168. channel_indexes=None):
  169. '''
  170. Return slices of the :class:`AnalogSignalArrays` in the
  171. :class:`Segment` that correspond to the :attr:`channel_indexes`
  172. provided.
  173. '''
  174. if channel_indexes is None:
  175. return []
  176. sliced_sigarrays = []
  177. for sigarr in self.analogsignals:
  178. if sigarr.get_channel_index() is not None:
  179. ind = np.in1d(sigarr.get_channel_index(), channel_indexes)
  180. sliced_sigarrays.append(sigarr[:, ind])
  181. return sliced_sigarrays
  182. def construct_subsegment_by_unit(self, unit_list=None):
  183. '''
  184. Return a new :class:`Segment that contains the :class:`AnalogSignal`,
  185. :class:`AnalogSignal`, and :class:`SpikeTrain`
  186. objects common to both the current :class:`Segment` and any
  187. :class:`Unit` in the :attr:`unit_list` provided.
  188. *Example*::
  189. >>> from neo.core import (Segment, Block, Unit, SpikeTrain,
  190. ... ChannelIndex)
  191. >>>
  192. >>> blk = Block()
  193. >>> chx = ChannelIndex(name='group0')
  194. >>> blk.channel_indexes = [chx]
  195. >>>
  196. >>> for ind in range(5):
  197. ... unit = Unit(name='Unit #%s' % ind, channel_index=ind)
  198. ... chx.units.append(unit)
  199. ...
  200. >>>
  201. >>> for ind in range(3):
  202. ... seg = Segment(name='Simulation #%s' % ind)
  203. ... blk.segments.append(seg)
  204. ... for unit in chx.units:
  205. ... train = SpikeTrain([1, 2, 3], units='ms', t_start=0.,
  206. ... t_stop=10)
  207. ... train.unit = unit
  208. ... unit.spiketrains.append(train)
  209. ... seg.spiketrains.append(train)
  210. ...
  211. >>>
  212. >>> seg0 = blk.segments[-1]
  213. >>> seg1 = seg0.construct_subsegment_by_unit(chx.units[:2])
  214. >>> len(seg0.spiketrains)
  215. 5
  216. >>> len(seg1.spiketrains)
  217. 2
  218. '''
  219. # todo: provide equivalent method using Group/ChannelView
  220. # add deprecation message (use decorator)?
  221. seg = Segment()
  222. seg.spiketrains = self.take_spiketrains_by_unit(unit_list)
  223. seg.analogsignals = \
  224. self.take_slice_of_analogsignalarray_by_unit(unit_list)
  225. # TODO copy others attributes
  226. return seg
  227. def time_slice(self, t_start=None, t_stop=None, reset_time=False, **kwargs):
  228. """
  229. Creates a time slice of a Segment containing slices of all child
  230. objects.
  231. Parameters:
  232. -----------
  233. t_start: Quantity
  234. Starting time of the sliced time window.
  235. t_stop: Quantity
  236. Stop time of the sliced time window.
  237. reset_time: bool
  238. If True the time stamps of all sliced objects are set to fall
  239. in the range from t_start to t_stop.
  240. If False, original time stamps are retained.
  241. Default is False.
  242. Keyword Arguments:
  243. ------------------
  244. Additional keyword arguments used for initialization of the sliced
  245. Segment object.
  246. Returns:
  247. --------
  248. subseg: Segment
  249. Temporal slice of the original Segment from t_start to t_stop.
  250. """
  251. subseg = Segment(**kwargs)
  252. for attr in ['file_datetime', 'rec_datetime', 'index',
  253. 'name', 'description', 'file_origin']:
  254. setattr(subseg, attr, getattr(self, attr))
  255. subseg.annotations = deepcopy(self.annotations)
  256. if t_start is None:
  257. t_start = self.t_start
  258. if t_stop is None:
  259. t_stop = self.t_stop
  260. t_shift = - t_start
  261. # cut analogsignals and analogsignalarrays
  262. for ana_id in range(len(self.analogsignals)):
  263. if hasattr(self.analogsignals[ana_id], '_rawio'):
  264. ana_time_slice = self.analogsignals[ana_id].load(time_slice=(t_start, t_stop))
  265. else:
  266. ana_time_slice = self.analogsignals[ana_id].time_slice(t_start, t_stop)
  267. if reset_time:
  268. ana_time_slice = ana_time_slice.time_shift(t_shift)
  269. subseg.analogsignals.append(ana_time_slice)
  270. # cut irregularly sampled signals
  271. for irr_id in range(len(self.irregularlysampledsignals)):
  272. if hasattr(self.irregularlysampledsignals[irr_id], '_rawio'):
  273. ana_time_slice = self.irregularlysampledsignals[irr_id].load(
  274. time_slice=(t_start, t_stop))
  275. else:
  276. ana_time_slice = self.irregularlysampledsignals[irr_id].time_slice(t_start, t_stop)
  277. if reset_time:
  278. ana_time_slice = ana_time_slice.time_shift(t_shift)
  279. subseg.irregularlysampledsignals.append(ana_time_slice)
  280. # cut spiketrains
  281. for st_id in range(len(self.spiketrains)):
  282. if hasattr(self.spiketrains[st_id], '_rawio'):
  283. st_time_slice = self.spiketrains[st_id].load(time_slice=(t_start, t_stop))
  284. else:
  285. st_time_slice = self.spiketrains[st_id].time_slice(t_start, t_stop)
  286. if reset_time:
  287. st_time_slice = st_time_slice.time_shift(t_shift)
  288. subseg.spiketrains.append(st_time_slice)
  289. # cut events
  290. for ev_id in range(len(self.events)):
  291. if hasattr(self.events[ev_id], '_rawio'):
  292. ev_time_slice = self.events[ev_id].load(time_slice=(t_start, t_stop))
  293. else:
  294. ev_time_slice = self.events[ev_id].time_slice(t_start, t_stop)
  295. if reset_time:
  296. ev_time_slice = ev_time_slice.time_shift(t_shift)
  297. # appending only non-empty events
  298. if len(ev_time_slice):
  299. subseg.events.append(ev_time_slice)
  300. # cut epochs
  301. for ep_id in range(len(self.epochs)):
  302. if hasattr(self.epochs[ep_id], '_rawio'):
  303. ep_time_slice = self.epochs[ep_id].load(time_slice=(t_start, t_stop))
  304. else:
  305. ep_time_slice = self.epochs[ep_id].time_slice(t_start, t_stop)
  306. if reset_time:
  307. ep_time_slice = ep_time_slice.time_shift(t_shift)
  308. # appending only non-empty epochs
  309. if len(ep_time_slice):
  310. subseg.epochs.append(ep_time_slice)
  311. subseg.create_relationship()
  312. return subseg