Scheduled service maintenance on November 22


On Friday, November 22, 2024, between 06:00 CET and 18:00 CET, GIN services will undergo planned maintenance. Extended service interruptions should be expected. We will try to keep downtimes to a minimum, but recommend that users avoid critical tasks, large data uploads, or DOI requests during this time.

We apologize for any inconvenience.

neo_utils.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. '''
  2. Convenience functions to extend the functionality of the Neo framework
  3. version 0.5.
  4. Authors: Julia Sprenger, Lyuba Zehl, Michael Denker
  5. Copyright (c) 2017, Institute of Neuroscience and Medicine (INM-6),
  6. Forschungszentrum Juelich, Germany
  7. All rights reserved.
  8. Redistribution and use in source and binary forms, with or without
  9. modification, are permitted provided that the following conditions are met:
  10. * Redistributions of source code must retain the above copyright notice, this
  11. list of conditions and the following disclaimer.
  12. * Redistributions in binary form must reproduce the above copyright notice,
  13. this list of conditions and the following disclaimer in the documentation
  14. and/or other materials provided with the distribution.
  15. * Neither the names of the copyright holders nor the names of the contributors
  16. may be used to endorse or promote products derived from this software without
  17. specific prior written permission.
  18. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  22. FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  23. DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  24. SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  25. CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  26. OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  27. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. '''
  29. import copy
  30. import warnings
  31. import inspect
  32. import numpy as np
  33. import quantities as pq
  34. import neo
  35. def get_events(container, properties=None):
  36. """
  37. This function returns a list of Neo Event objects, corresponding to given
  38. key-value pairs in the attributes or annotations of the Event.
  39. Parameter:
  40. ---------
  41. container: neo.Block or neo.Segment
  42. The Neo Block or Segment object to extract data from.
  43. properties: dictionary
  44. A dictionary that contains the Event keys and values to filter for.
  45. Each key of the dictionary is matched to a attribute or an an
  46. annotation of Event. The value of each dictionary entry corresponds to
  47. a valid entry or a list of valid entries of the attribute or
  48. annotation.
  49. If the value belonging to the key is a list of entries of the same
  50. length as the number of events in the Event object, the list entries
  51. are matched to the events in the Event object. The resulting Event
  52. object contains only those events where the values match up.
  53. Otherwise, the value is compared to the attributes or annotation of the
  54. Event object as such, and depending on the comparison, either the
  55. complete Event object is returned or not.
  56. If None or an empty dictionary is passed, all Event Objects will be
  57. returned in a list.
  58. Returns:
  59. --------
  60. events: list
  61. A list of Event objects matching the given criteria.
  62. Example:
  63. --------
  64. >>> event = neo.Event(
  65. times = [0.5, 10.0, 25.2] * pq.s)
  66. >>> event.annotate(
  67. event_type = 'trial start',
  68. trial_id = [1, 2, 3]
  69. >>> seg = neo.Segment()
  70. >>> seg.events = [event]
  71. # Will return a list with the complete event object
  72. >>> get_events(event, properties={event_type='trial start')
  73. # Will return an empty list
  74. >>> get_events(event, properties={event_type='trial stop'})
  75. # Will return a list with an Event object, but only with trial 2
  76. >>> get_events(event, properties={'trial_id' = 2})
  77. # Will return a list with an Event object, but only with trials 1 and 2
  78. >>> get_events(event, properties={'trial_id' = [1, 2]})
  79. """
  80. if isinstance(container, neo.Segment):
  81. return _get_from_list(container.events, prop=properties)
  82. elif isinstance(container, neo.Block):
  83. event_lst = []
  84. for seg in container.segments:
  85. event_lst += _get_from_list(seg.events, prop=properties)
  86. return event_lst
  87. else:
  88. raise TypeError(
  89. 'Container needs to be of type neo.Block or neo.Segment, not %s '
  90. 'in order to extract Events.' % (type(container)))
  91. def get_epochs(container, properties=None):
  92. """
  93. This function returns a list of Neo Epoch objects, corresponding to given
  94. key-value pairs in the attributes or annotations of the Epoch.
  95. Parameters:
  96. -----------
  97. container: neo.Block or neo.Segment
  98. The Neo Block or Segment object to extract data from.
  99. properties: dictionary
  100. A dictionary that contains the Epoch keys and values to filter for.
  101. Each key of the dictionary is matched to an attribute or an an
  102. annotation of the Event. The value of each dictionary entry corresponds
  103. to a valid entry or a list of valid entries of the attribute or
  104. annotation.
  105. If the value belonging to the key is a list of entries of the same
  106. length as the number of epochs in the Epoch object, the list entries
  107. are matched to the epochs in the Epoch object. The resulting Epoch
  108. object contains only those epochs where the values match up.
  109. Otherwise, the value is compared to the attribute or annotation of the
  110. Epoch object as such, and depending on the comparison, either the
  111. complete Epoch object is returned or not.
  112. If None or an empty dictionary is passed, all Epoch Objects will
  113. be returned in a list.
  114. Returns:
  115. --------
  116. epochs: list
  117. A list of Epoch objects matching the given criteria.
  118. Example:
  119. --------
  120. >>> epoch = neo.Epoch(
  121. times = [0.5, 10.0, 25.2] * pq.s,
  122. durations = [100, 100, 100] * pq.ms)
  123. >>> epoch.annotate(
  124. event_type = 'complete trial',
  125. trial_id = [1, 2, 3]
  126. >>> seg = neo.Segment()
  127. >>> seg.epochs = [epoch]
  128. # Will return a list with the complete event object
  129. >>> get_epochs(epoch, prop={epoch_type='complete trial')
  130. # Will return an empty list
  131. >>> get_epochs(epoch, prop={epoch_type='error trial'})
  132. # Will return a list with an Event object, but only with trial 2
  133. >>> get_epochs(epoch, prop={'trial_id' = 2})
  134. # Will return a list with an Event object, but only with trials 1 and 2
  135. >>> get_epochs(epoch, prop={'trial_id' = [1, 2]})
  136. """
  137. if isinstance(container, neo.Segment):
  138. return _get_from_list(container.epochs, prop=properties)
  139. elif isinstance(container, neo.Block):
  140. epoch_list = []
  141. for seg in container.segments:
  142. epoch_list += _get_from_list(seg.epochs, prop=properties)
  143. return epoch_list
  144. else:
  145. raise TypeError(
  146. 'Container needs to be of type neo.Block or neo.Segment, not %s '
  147. 'in order to extract Epochs.' % (type(container)))
  148. def add_epoch(
  149. segment, event1, event2=None, pre=0 * pq.s, post=0 * pq.s,
  150. attach_result=True, **kwargs):
  151. """
  152. Create epochs around a single event, or between pairs of events. Starting
  153. and end time of the epoch can be modified using pre and post as offsets
  154. before the and after the event(s). Additional keywords will be directly
  155. forwarded to the epoch intialization.
  156. Parameters:
  157. -----------
  158. sgement : neo.Segment
  159. The segement in which the final Epoch object is added.
  160. event1 : neo.Event
  161. The Neo Event objects containing the start events of the epochs. If no
  162. event2 is specified, these event1 also specifies the stop events, i.e.,
  163. the epoch is cut around event1 times.
  164. event2: neo.Event
  165. The Neo Event objects containing the stop events of the epochs. If no
  166. event2 is specified, event1 specifies the stop events, i.e., the epoch
  167. is cut around event1 times. The number of events in event2 must match
  168. that of event1.
  169. pre, post: Quantity (time)
  170. Time offsets to modify the start (pre) and end (post) of the resulting
  171. epoch. Example: pre=-10*ms and post=+25*ms will cut from 10 ms before
  172. event1 times to 25 ms after event2 times
  173. attach_result: bool
  174. If True, the resulting Neo Epoch object is added to segment.
  175. Keyword Arguments:
  176. ------------------
  177. Passed to the Neo Epoch object.
  178. Returns:
  179. --------
  180. epoch: neo.Epoch
  181. An Epoch object with the calculated epochs (one per entry in event1).
  182. """
  183. if event2 is None:
  184. event2 = event1
  185. if not isinstance(segment, neo.Segment):
  186. raise TypeError(
  187. 'Segment has to be of type neo.Segment, not %s' % type(segment))
  188. for event in [event1, event2]:
  189. if not isinstance(event, neo.Event):
  190. raise TypeError(
  191. 'Events have to be of type neo.Event, not %s' % type(event))
  192. if len(event1) != len(event2):
  193. raise ValueError(
  194. 'event1 and event2 have to have the same number of entries in '
  195. 'order to create epochs between pairs of entries. Match your '
  196. 'events before generating epochs. Current event lengths '
  197. 'are %i and %i' % (len(event1), len(event2)))
  198. times = event1.times + pre
  199. durations = event2.times + post - times
  200. if any(durations < 0):
  201. raise ValueError(
  202. 'Can not create epoch with negative duration. '
  203. 'Requested durations %s.' % durations)
  204. elif any(durations == 0):
  205. raise ValueError('Can not create epoch with zero duration.')
  206. if 'name' not in kwargs:
  207. kwargs['name'] = 'epoch'
  208. if 'labels' not in kwargs:
  209. kwargs['labels'] = [
  210. '%s_%i' % (kwargs['name'], i) for i in range(len(times))]
  211. ep = neo.Epoch(times=times, durations=durations, **kwargs)
  212. ep.annotate(**event1.annotations)
  213. if attach_result:
  214. segment.epochs.append(ep)
  215. segment.create_relationship()
  216. return ep
  217. def match_events(event1, event2):
  218. """
  219. Finds pairs of Event entries in event1 and event2 with the minimum delay,
  220. such that the entry of event1 directly preceeds the entry of event2.
  221. Returns filtered two events of identical length, which contain matched
  222. entries.
  223. Parameters:
  224. -----------
  225. event1, event2: neo.Event
  226. The two Event objects to match up.
  227. Returns:
  228. --------
  229. event1, event2: neo.Event
  230. Event objects with identical number of events, containing only those
  231. events that could be matched against each other. A warning is issued if
  232. not all events in event1 or event2 could be matched.
  233. """
  234. event1 = event1
  235. event2 = event2
  236. id1, id2 = 0, 0
  237. match_ev1, match_ev2 = [], []
  238. while id1 < len(event1) and id2 < len(event2):
  239. time1 = event1.times[id1]
  240. time2 = event2.times[id2]
  241. # wrong order of events
  242. if time1 > time2:
  243. id2 += 1
  244. # shorter epoch possible by later event1 entry
  245. elif id1 + 1 < len(event1) and event1.times[id1 + 1] < time2:
  246. # there is no event in 2 until the next event in 1
  247. id1 += 1
  248. # found a match
  249. else:
  250. match_ev1.append(id1)
  251. match_ev2.append(id2)
  252. id1 += 1
  253. id2 += 1
  254. if id1 < len(event1):
  255. warnings.warn(
  256. 'Could not match all events to generate epochs. Missed '
  257. '%s event entries in event1 list' % (len(event1) - id1))
  258. if id2 < len(event2):
  259. warnings.warn(
  260. 'Could not match all events to generate epochs. Missed '
  261. '%s event entries in event2 list' % (len(event2) - id2))
  262. event1_matched = _event_epoch_slice_by_valid_ids(
  263. obj=event1, valid_ids=match_ev1)
  264. event2_matched = _event_epoch_slice_by_valid_ids(
  265. obj=event2, valid_ids=match_ev2)
  266. return event1_matched, event2_matched
  267. def cut_block_by_epochs(block, properties=None, reset_time=False):
  268. """
  269. This function cuts Neo Segments in a Neo Block according to multiple Neo
  270. Epoch objects.
  271. The function alters the Neo Block by adding one Neo Segment per Epoch entry
  272. fulfilling a set of conditions on the Epoch attributes and annotations. The
  273. original segments are removed from the block.
  274. A dictionary contains restrictions on which epochs are considered for
  275. the cutting procedure. To this end, it is possible to
  276. specify accepted (valid) values of specific annotations on the source
  277. epochs.
  278. The resulting cut segments may either retain their original time stamps, or
  279. be shifted to a common starting time.
  280. Parameters
  281. ----------
  282. block: Neo Block
  283. Contains the Segments to cut according to the Epoch criteria provided
  284. properties: dictionary
  285. A dictionary that contains the Epoch keys and values to filter for.
  286. Each key of the dictionary is matched to an attribute or an an
  287. annotation of the Event. The value of each dictionary entry corresponds
  288. to a valid entry or a list of valid entries of the attribute or
  289. annotation.
  290. If the value belonging to the key is a list of entries of the same
  291. length as the number of epochs in the Epoch object, the list entries
  292. are matched to the epochs in the Epoch object. The resulting Epoch
  293. object contains only those epochs where the values match up.
  294. Otherwise, the value is compared to the attributes or annotation of the
  295. Epoch object as such, and depending on the comparison, either the
  296. complete Epoch object is returned or not.
  297. If None or an empty dictionary is passed, all Epoch Objects will
  298. be considered
  299. reset_time: bool
  300. If True the times stamps of all sliced objects are set to fall
  301. in the range from 0 to the duration of the epoch duration.
  302. If False, original time stamps are retained.
  303. Default is False.
  304. Returns:
  305. --------
  306. None
  307. """
  308. if not isinstance(block, neo.Block):
  309. raise TypeError(
  310. 'block needs to be a neo Block, not %s' % type(block))
  311. old_segments = copy.copy(block.segments)
  312. for seg in old_segments:
  313. epochs = _get_from_list(seg.epochs, prop=properties)
  314. if len(epochs) > 1:
  315. warnings.warn(
  316. 'Segment %s contains multiple epochs with '
  317. 'requested properties (%s). Subsegments can '
  318. 'have overlapping times' % (seg.name, properties))
  319. elif len(epochs) == 0:
  320. warnings.warn(
  321. 'No epoch is matching the requested epoch properties %s. '
  322. 'No cutting of segment performed.' % (properties))
  323. for epoch in epochs:
  324. new_segments = cut_segment_by_epoch(
  325. seg, epoch=epoch, reset_time=reset_time)
  326. block.segments += new_segments
  327. block.segments.remove(seg)
  328. block.create_relationship()
  329. def cut_segment_by_epoch(seg, epoch, reset_time=False):
  330. """
  331. Cuts a Neo Segment according to a neo Epoch object
  332. The function returns a list of neo Segments, where each segment corresponds
  333. to an epoch in the neo Epoch object and contains the data of the original
  334. Segment cut to that particular Epoch.
  335. The resulting segments may either retain their original time stamps,
  336. or can be shifted to a common time axis.
  337. Parameters
  338. ----------
  339. seg: Neo Segment
  340. The Segment containing the original uncut data.
  341. epoch: Neo Epoch
  342. For each epoch in this input, one segment is generated according to
  343. the epoch time and duration.
  344. reset_time: bool
  345. If True the times stamps of all sliced objects are set to fall
  346. in the range from 0 to the duration of the epoch duration.
  347. If False, original time stamps are retained.
  348. Default is False.
  349. Returns:
  350. --------
  351. segments: list of Neo Segments
  352. Per epoch in the input, a neo.Segment with AnalogSignal and/or
  353. SpikeTrain Objects will be generated and returned. Each Segment will
  354. receive the annotations of the corresponding epoch in the input.
  355. """
  356. if not isinstance(seg, neo.Segment):
  357. raise TypeError(
  358. 'Seg needs to be of type neo.Segment, not %s' % type(seg))
  359. if type(seg.parents[0]) != neo.Block:
  360. raise ValueError(
  361. 'Segment has no block as parent. Can not cut segment.')
  362. if not isinstance(epoch, neo.Epoch):
  363. raise TypeError(
  364. 'Epoch needs to be of type neo.Epoch, not %s' % type(epoch))
  365. segments = []
  366. for ep_id in range(len(epoch)):
  367. subseg = seg_time_slice(seg,
  368. epoch.times[ep_id],
  369. epoch.times[ep_id] + epoch.durations[ep_id],
  370. reset_time=reset_time)
  371. # Add annotations of Epoch
  372. for a in epoch.annotations:
  373. if type(epoch.annotations[a]) is list \
  374. and len(epoch.annotations[a]) == len(epoch):
  375. subseg.annotations[a] = copy.copy(epoch.annotations[a][ep_id])
  376. else:
  377. subseg.annotations[a] = copy.copy(epoch.annotations[a])
  378. segments.append(subseg)
  379. return segments
  380. def seg_time_slice(seg, t_start=None, t_stop=None, reset_time=False, **kwargs):
  381. """
  382. Creates a time slice of a neo Segment containing slices of all child
  383. objects.
  384. Parameters:
  385. -----------
  386. seg: neo Segment
  387. The neo Segment object to slice.
  388. t_start: Quantity
  389. Starting time of the sliced time window.
  390. t_stop: Quantity
  391. Stop time of the sliced time window.
  392. reset_time: bool
  393. If True the times stamps of all sliced objects are set to fall
  394. in the range from 0 to the duration of the epoch duration.
  395. If False, original time stamps are retained.
  396. Default is False.
  397. Keyword Arguments:
  398. ------------------
  399. Additional keyword arguments used for initialization of the sliced
  400. Neo Segment object.
  401. Returns:
  402. --------
  403. seg: Neo Segment
  404. Temporal slice of the original Neo Segment from t_start to t_stop.
  405. """
  406. subseg = neo.Segment(**kwargs)
  407. for attr in [
  408. 'file_datetime', 'rec_datetime', 'index',
  409. 'name', 'description', 'file_origin']:
  410. setattr(subseg, attr, getattr(seg, attr))
  411. subseg.annotations = copy.deepcopy(seg.annotations)
  412. # This would be the better definition of t_shift after incorporating
  413. # PR#215 at NeuronalEnsemble/python-neo
  414. t_shift = seg.t_start - t_start
  415. # t_min_id = np.argmin(np.array([a.t_start for a in seg.analogsignals]))
  416. # t_shift = seg.analogsignals[t_min_id] - t_start
  417. # cut analogsignals
  418. for ana_id in range(len(seg.analogsignals)):
  419. ana_time_slice = seg.analogsignals[ana_id].time_slice(t_start, t_stop)
  420. # explicitely copying parents as this is not yet fixed in neo (
  421. # NeuralEnsemble/python-neo issue #220)
  422. ana_time_slice.segment = subseg
  423. ana_time_slice.channel_index = seg.analogsignals[ana_id].channel_index
  424. if reset_time:
  425. ana_time_slice.t_start = ana_time_slice.t_start + t_shift
  426. subseg.analogsignals.append(ana_time_slice)
  427. # cut spiketrains
  428. for st_id in range(len(seg.spiketrains)):
  429. st_time_slice = seg.spiketrains[st_id].time_slice(t_start, t_stop)
  430. if reset_time:
  431. st_time_slice = shift_spiketrain(st_time_slice, t_shift)
  432. subseg.spiketrains.append(st_time_slice)
  433. # cut events
  434. for ev_id in range(len(seg.events)):
  435. ev_time_slice = event_time_slice(seg.events[ev_id], t_start, t_stop)
  436. if reset_time:
  437. ev_time_slice = shift_event(ev_time_slice, t_shift)
  438. # appending only non-empty events
  439. if len(ev_time_slice):
  440. subseg.events.append(ev_time_slice)
  441. # cut epochs
  442. for ep_id in range(len(seg.epochs)):
  443. ep_time_slice = epoch_time_slice(seg.epochs[ep_id], t_start, t_stop)
  444. if reset_time:
  445. ep_time_slice = shift_epoch(ep_time_slice, t_shift)
  446. # appending only non-empty epochs
  447. if len(ep_time_slice):
  448. subseg.epochs.append(ep_time_slice)
  449. # TODO: Improve
  450. # seg.create_relationship(force=True)
  451. return subseg
  452. def shift_spiketrain(spiketrain, t_shift):
  453. """
  454. Shifts a spike train to start at a new time.
  455. Parameters:
  456. -----------
  457. spiketrain: Neo SpikeTrain
  458. Spiketrain of which a copy will be generated with shifted spikes and
  459. starting and stopping times
  460. t_shift: Quantity (time)
  461. Amount of time by which to shift the SpikeTrain.
  462. Returns:
  463. --------
  464. spiketrain: Neo SpikeTrain
  465. New instance of a SpikeTrain object starting at t_start (the original
  466. SpikeTrain is not modified).
  467. """
  468. new_st = spiketrain.duplicate_with_new_data(
  469. signal=spiketrain.times.view(pq.Quantity) + t_shift,
  470. t_start=spiketrain.t_start + t_shift,
  471. t_stop=spiketrain.t_stop + t_shift)
  472. return new_st
  473. def shift_event(ev, t_shift):
  474. """
  475. Shifts an event by an amount of time.
  476. Parameters:
  477. -----------
  478. event: Neo Event
  479. Event of which a copy will be generated with shifted times
  480. t_shift: Quantity (time)
  481. Amount of time by which to shift the Event.
  482. Returns:
  483. --------
  484. epoch: Neo Event
  485. New instance of an Event object starting at t_shift later than the
  486. original Event (the original Event is not modified).
  487. """
  488. return _shift_time_signal(ev, t_shift)
  489. def shift_epoch(epoch, t_shift):
  490. """
  491. Shifts an epoch by an amount of time.
  492. Parameters:
  493. -----------
  494. epoch: Neo Epoch
  495. Epoch of which a copy will be generated with shifted times
  496. t_shift: Quantity (time)
  497. Amount of time by which to shift the Epoch.
  498. Returns:
  499. --------
  500. epoch: Neo Epoch
  501. New instance of an Epoch object starting at t_shift later than the
  502. original Epoch (the original Epoch is not modified).
  503. """
  504. return _shift_time_signal(epoch, t_shift)
  505. def event_time_slice(event, t_start=None, t_stop=None):
  506. """
  507. Slices an Event object to retain only those events that fall in a certain
  508. time window.
  509. Parameters:
  510. -----------
  511. event: Neo Event
  512. The Event to slice.
  513. t_start, t_stop: Quantity (time)
  514. Time window in which to retain events. An event at time t is retained
  515. if t_start <= t < t_stop.
  516. Returns:
  517. --------
  518. event: Neo Event
  519. New instance of an Event object containing only the events in the time
  520. range.
  521. """
  522. if t_start is None:
  523. t_start = -np.inf
  524. if t_stop is None:
  525. t_stop = np.inf
  526. valid_ids = np.where(np.logical_and(
  527. event.times >= t_start, event.times < t_stop))[0]
  528. new_event = _event_epoch_slice_by_valid_ids(event, valid_ids=valid_ids)
  529. return new_event
  530. def epoch_time_slice(epoch, t_start=None, t_stop=None):
  531. """
  532. Slices an Epoch object to retain only those epochs that fall in a certain
  533. time window.
  534. Parameters:
  535. -----------
  536. epoch: Neo Epoch
  537. The Epoch to slice.
  538. t_start, t_stop: Quantity (time)
  539. Time window in which to retain epochs. An epoch at time t and
  540. duration d is retained if t_start <= t < t_stop - d.
  541. Returns:
  542. --------
  543. epoch: Neo Epoch
  544. New instance of an Epoch object containing only the epochs in the time
  545. range.
  546. """
  547. if t_start is None:
  548. t_start = -np.inf
  549. if t_stop is None:
  550. t_stop = np.inf
  551. valid_ids = np.where(np.logical_and(
  552. epoch.times >= t_start, epoch.times + epoch.durations < t_stop))[0]
  553. new_epoch = _event_epoch_slice_by_valid_ids(epoch, valid_ids=valid_ids)
  554. return new_epoch
  555. def _get_from_list(input_list, prop=None):
  556. """
  557. Internal function
  558. """
  559. output_list = []
  560. # empty or no dictionary
  561. if not prop or bool([b for b in prop.values() if b == []]):
  562. output_list += [e for e in input_list]
  563. # dictionary is given
  564. else:
  565. for ep in input_list:
  566. sparse_ep = ep.copy()
  567. for k in prop.keys():
  568. sparse_ep = _filter_event_epoch(sparse_ep, k, prop[k])
  569. # if there is nothing left, it cannot filtered
  570. if sparse_ep is None:
  571. break
  572. if sparse_ep is not None:
  573. output_list.append(sparse_ep)
  574. return output_list
  575. def _filter_event_epoch(obj, annotation_key, annotation_value):
  576. """
  577. Internal function.
  578. This function return a copy of a neo Event or Epoch object, which only
  579. contains attributes or annotations corresponding to requested key-value
  580. pairs.
  581. Parameters:
  582. -----------
  583. obj : neo.Event
  584. The neo Event or Epoch object to modify.
  585. annotation_key : string, int or float
  586. The name of the annotation used to filter.
  587. annotation_value : string, int, float, list or np.ndarray
  588. The accepted value or list of accepted values of the attributes or
  589. annotations specified by annotation_key. For each entry in obj the
  590. respective annotation defined by annotation_key is compared to the
  591. annotation value. The entry of obj is kept if the attribute or
  592. annotation is equal or contained in annotation_value.
  593. Returns:
  594. --------
  595. obj : neo.Event or neo.Epoch
  596. The Event or Epoch object with every event or epoch removed that does
  597. not match the filter criteria (i.e., where none of the entries in
  598. annotation_value match the attribute or annotation annotation_key.
  599. """
  600. valid_ids = _get_valid_ids(obj, annotation_key, annotation_value)
  601. if len(valid_ids) == 0:
  602. return None
  603. return _event_epoch_slice_by_valid_ids(obj, valid_ids)
  604. def _event_epoch_slice_by_valid_ids(obj, valid_ids):
  605. """
  606. Internal function
  607. """
  608. # modify annotations
  609. sparse_annotations = _get_valid_annotations(obj, valid_ids)
  610. # modify labels
  611. sparse_labels = _get_valid_labels(obj, valid_ids)
  612. if type(obj) is neo.Event:
  613. sparse_obj = neo.Event(
  614. times=copy.deepcopy(obj.times[valid_ids]),
  615. labels=sparse_labels,
  616. units=copy.deepcopy(obj.units),
  617. name=copy.deepcopy(obj.name),
  618. description=copy.deepcopy(obj.description),
  619. file_origin=copy.deepcopy(obj.file_origin),
  620. **sparse_annotations)
  621. elif type(obj) is neo.Epoch:
  622. sparse_obj = neo.Epoch(
  623. times=copy.deepcopy(obj.times[valid_ids]),
  624. durations=copy.deepcopy(obj.durations[valid_ids]),
  625. labels=sparse_labels,
  626. units=copy.deepcopy(obj.units),
  627. name=copy.deepcopy(obj.name),
  628. description=copy.deepcopy(obj.description),
  629. file_origin=copy.deepcopy(obj.file_origin),
  630. **sparse_annotations)
  631. else:
  632. raise TypeError('Can only slice Event and Epoch objects by valid IDs.')
  633. return sparse_obj
  634. def _get_valid_ids(obj, annotation_key, annotation_value):
  635. """
  636. Internal function
  637. """
  638. # wrap annotation value to be list
  639. if not type(annotation_value) in [list, np.ndarray]:
  640. annotation_value = [annotation_value]
  641. # get all real attributes of object
  642. attributes = inspect.getmembers(obj)
  643. attributes_names = [t[0] for t in attributes if not(
  644. t[0].startswith('__') and t[0].endswith('__'))]
  645. attributes_ids = [i for i, t in enumerate(attributes) if not(
  646. t[0].startswith('__') and t[0].endswith('__'))]
  647. # check if annotation is present
  648. value_avail = False
  649. if annotation_key in obj.annotations:
  650. check_value = obj.annotations[annotation_key]
  651. value_avail = True
  652. elif annotation_key in attributes_names:
  653. check_value = attributes[attributes_ids[
  654. attributes_names.index(annotation_key)]][1]
  655. value_avail = True
  656. if value_avail:
  657. # check if annotation is list and fits to length of object list
  658. if not _is_annotation_list(check_value, len(obj)):
  659. # check if annotation is single value and fits to requested value
  660. if (check_value in annotation_value):
  661. valid_mask = np.ones(obj.shape)
  662. else:
  663. valid_mask = np.zeros(obj.shape)
  664. if type(check_value) != str:
  665. warnings.warn(
  666. 'Length of annotation "%s" (%s) does not fit '
  667. 'to length of object list (%s)' % (
  668. annotation_key, len(check_value), len(obj)))
  669. # extract object entries, which match requested annotation
  670. else:
  671. valid_mask = np.zeros(obj.shape)
  672. for obj_id in range(len(obj)):
  673. if check_value[obj_id] in annotation_value:
  674. valid_mask[obj_id] = True
  675. else:
  676. valid_mask = np.zeros(obj.shape)
  677. valid_ids = np.where(valid_mask)[0]
  678. return valid_ids
  679. def _get_valid_annotations(obj, valid_ids):
  680. """
  681. Internal function
  682. """
  683. sparse_annotations = copy.deepcopy(obj.annotations)
  684. for key in sparse_annotations:
  685. if _is_annotation_list(sparse_annotations[key], len(obj)):
  686. sparse_annotations[key] = list(np.array(sparse_annotations[key])[
  687. valid_ids])
  688. return sparse_annotations
  689. def _get_valid_labels(obj, valid_ids):
  690. """
  691. Internal function
  692. """
  693. labels = obj.labels
  694. selected_labels = []
  695. if len(labels) > 0:
  696. if _is_annotation_list(labels, len(obj)):
  697. for vid in valid_ids:
  698. selected_labels.append(labels[vid])
  699. # sparse_labels = sparse_labels[valid_ids]
  700. else:
  701. warnings.warn('Can not filter object labels. Shape (%s) does not '
  702. 'fit object shape (%s)'
  703. '' % (labels.shape, obj.shape))
  704. return np.array(selected_labels)
  705. def _is_annotation_list(value, exp_length):
  706. """
  707. Internal function
  708. """
  709. return (
  710. (isinstance(value, list) or (
  711. isinstance(value, np.ndarray) and value.ndim > 0)) and
  712. (len(value) == exp_length))
  713. def _shift_time_signal(sig, t_shift):
  714. """
  715. Internal function.
  716. """
  717. if not hasattr(sig, 'times'):
  718. raise AttributeError(
  719. 'Can only shift signals, which have an attribute'
  720. ' "times", not %s' % type(sig))
  721. new_sig = sig.duplicate_with_new_data(signal=sig.times + t_shift)
  722. return new_sig