reachgraspio.py 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575
  1. # coding=utf-8
  2. '''
  3. Reach-to-grasp IO module
  4. This module provides an IO to load data recorded in the context of the reach-
  5. to-grasp experiments conducted by Thomas Brochier and Alexa Riehle at the
  6. Institute de Neurosciences de la Timone. The IO is based on the BlackrockIO of
  7. the Neo library, which is used in the background to load the primary data, and
  8. utilized the odML library to load metadata information. Specifically, this IO
  9. annotates the Neo object returned by BlackrockIO with semantic information,
  10. e.g., interpretation of digital event codes, and key-value pairs found in the
  11. corresponding odML file are attached to relevant Neo objects as annotations.
  12. Authors: Julia Sprenger, Lyuba Zehl, Michael Denker
  13. Copyright (c) 2017, Institute of Neuroscience and Medicine (INM-6),
  14. Forschungszentrum Juelich, Germany
  15. All rights reserved.
  16. Redistribution and use in source and binary forms, with or without
  17. modification, are permitted provided that the following conditions are met:
  18. * Redistributions of source code must retain the above copyright notice, this
  19. list of conditions and the following disclaimer.
  20. * Redistributions in binary form must reproduce the above copyright notice,
  21. this list of conditions and the following disclaimer in the documentation
  22. and/or other materials provided with the distribution.
  23. * Neither the names of the copyright holders nor the names of the contributors
  24. may be used to endorse or promote products derived from this software without
  25. specific prior written permission.
  26. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  27. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  28. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  29. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  30. FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  31. DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  32. SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  33. CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  34. OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  35. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36. '''
  37. import glob
  38. import os
  39. import re
  40. import numpy as np
  41. import odml.tools
  42. import quantities as pq
  43. import neo
  44. # Using old version of IO as long as available - should be deleted at some point
  45. try:
  46. from neo.io import OldBlackrockIO as BlackrockIO
  47. except ImportError:
  48. from neo.io.blackrockio import BlackrockIO
  49. class ReachGraspIO(BlackrockIO):
  50. """
  51. Derived class from Neo's BlackrockIO to load recordings obtained from the
  52. reach-to-grasp experiments.
  53. Args:
  54. filename (string):
  55. File name (without extension) of the set of Blackrock files to
  56. associate with. Any .nsX or .nev, .sif, or .ccf extensions are
  57. ignored when parsing this parameter. Note: unless the parameter
  58. nev_override is given, this IO will load the nev file containing
  59. the most recent spike sorted data of all nev files found in the
  60. same directory as filename. The spike sorting version is attached
  61. to filename by a postfix '-XX', where XX is the version, e.g.,
  62. l101010-001-02 for spike sorting version 2 of file l101010-001. If
  63. an odML file is specified, the version must be listed in the odML
  64. entries at
  65. "/PreProcessing/OfflineSpikeSorting/Sortings"
  66. and relates to the section
  67. "/PreProcessing/OfflineSpikeSorting/Sorting-XX".
  68. If no odML is present, no information on the spike sorting (e.g.,
  69. if a unit is SUA or MUA) is provided by this IO.
  70. odml_directory (string):
  71. Alternative directory where the odML file is stored. If None, the
  72. directory is assumed to be the same as the .nev and .nsX data
  73. files. Default: None.
  74. nsx_override (string):
  75. File name of the .nsX files (without extension). If None,
  76. filename is used.
  77. Default: None.
  78. nev_override (string):
  79. File name of the .nev file (without extension). If None, the
  80. current spike-sorted version filename is used (see parameter
  81. filename above). Default: None.
  82. sif_override (string):
  83. File name of the .sif file (without extension). If None,
  84. filename is used.
  85. Default: None.
  86. ccf_override (string):
  87. File name of the .ccf file (without extension). If None,
  88. filename is used.
  89. Default: None.
  90. odml_override (string):
  91. File name of the .odml file (without extension). If None,
  92. filename is used.
  93. Default: None.
  94. verbose (boolean):
  95. If True, the class will output additional diagnostic
  96. information on stdout.
  97. Default: False
  98. Returns:
  99. -
  100. Attributes:
  101. condition_str (dict):
  102. Dictionary containing a list of string codes reflecting the trial
  103. types that occur in recordings in a certain condition code
  104. (dictionary keys). For example, for condition 1 (all grip first
  105. conditions), condition_str[1] contains the list
  106. ['SGHF', 'SGLF', 'PGHF', 'PGLF'].
  107. Possible conditions:
  108. 0:[]
  109. No trials, or condition not conclusive from file
  110. 4 types (two_cues_task):
  111. 1: all grip-first trial types with two different cues
  112. 2: all force-first trial types with two different cues
  113. 2 types (two_cues_task):
  114. 11: grip-first, but only LF types
  115. 12: grip-first, but only HF types
  116. 13: grip-first, but only SG types
  117. 14: grip-first, but only PG types
  118. 2 types (two_cues_task):
  119. 21: force-first, but only LF types
  120. 22: force-first, but only HF types
  121. 23: force-first, but only SG types
  122. 24: force-first, but only PG types
  123. 1 type (two_cues_task):
  124. 131: grip-first, but only SGLF type
  125. 132: grip-first, but only SGHF type
  126. 141: grip-first, but only PGLF type
  127. 142: grip-first, but only PGHF type
  128. 213: force-first, but only LFSG type
  129. 214: force-first, but only LFPG type
  130. 223: force-first, but only HFSG type
  131. 224: force-first, but only HFPG type
  132. 1 type (one_cue_task):
  133. 133: SGSG, only grip info, force unknown
  134. 144: PGPG, only grip info, force unknown
  135. 211: LFLF, only force info, grip unknown
  136. 222: HFHF, only force info, grip unknown
  137. event_labels_str (dict):
  138. Provides a text label for each digital event code returned as
  139. events by the parent BlackrockIO. For example,
  140. event_labels_str['65296'] contains the string 'TS-ON'.
  141. event_labels_codes (dict):
  142. Reverse of `event_labels_str`: Provides a list of event codes
  143. related to a specific text label for a trial event. For example,
  144. event_labels_codes['TS-ON'] contains the list ['65296']. In
  145. addition to the detailed codes, for convenience the meta codes
  146. 'CUE/GO', 'RW-ON', and 'SR' summarizing a set of digital events are
  147. defined for easier access.
  148. trial_const_sequence_str (dict):
  149. Dictionary contains the ordering of selected constant trial events
  150. for correct trials, e.g., as TS is the first trial event in a
  151. correct trial, trial_const_sequence_codes['TS'] is 0.
  152. trial_const_sequence_codes (dict):
  153. Reverse of trial_const_sequence_str: Dictionary contains the
  154. ordering of selected constant trial events for correct trials,
  155. e.g., trial_const_sequence_codes[0] is 'TS'.
  156. performance_str (dict):
  157. Text strings to help interpret the performance code of a trial. For
  158. example, correct trials have a performance code of 255, and thus
  159. performance_str[255] == 'correct_trial'
  160. performance_codes (dict):
  161. Reverse of performance_const_sequence_str. Returns the performance
  162. code of a given text string indicating trial performance. For
  163. example, performance_str['correct_trial'] == 255
  164. """
  165. # Create a dictionary of conditions (i.e., the trial types presented in a
  166. # given recording session)
  167. condition_str = {
  168. 0: [],
  169. 1: ['SGHF', 'SGLF', 'PGHF', 'PGLF'],
  170. 2: ['HFSG', 'HFPG', 'LFSG', 'LFPG'],
  171. 11: ['SGLF', 'PGLF'],
  172. 12: ['SGHF', 'PGHF'],
  173. 13: ['SGHF', 'SGLF'],
  174. 14: ['PGHF', 'PGLF'],
  175. 21: ['LFSG', 'LFPG'],
  176. 22: ['HFSG', 'HFPG'],
  177. 23: ['HFSG', 'LFSG'],
  178. 24: ['HFPG', 'LFPG'],
  179. 131: ['SGLF'],
  180. 132: ['SGHF'],
  181. 133: ['SGSG'],
  182. 141: ['PGLF'],
  183. 142: ['PGHF'],
  184. 144: ['PGPG'],
  185. 211: ['LFLF'],
  186. 213: ['LFSG'],
  187. 214: ['LFPG'],
  188. 222: ['HFHF'],
  189. 223: ['HFSG'],
  190. 224: ['HFPG']}
  191. ###########################################################################
  192. # event labels, the corresponding first 8 digits of their binary
  193. # representation and their meaning
  194. #
  195. # R L T T L L L L
  196. # w E a r E E E E
  197. # P D S S D D D D in
  198. # u c w t b t t b mo-
  199. # l r l r nk-
  200. # label:| ^ ^ ^ ^ ^ ^ ^ ^ | status of devices: | trial event label:| ey
  201. # 65280 < 0 0 0 0 0 0 0 0 > TS-OFF > TS-OFF/STOP > L,T
  202. # 65296 < 0 0 0 1 0 0 0 0 > TS-ON > TS-ON > all
  203. # 65312 < 0 0 1 0 0 0 0 0 > TaSw > STOP > all
  204. # 65344 < 0 1 0 0 0 0 0 0 > LEDc (+TS-OFF) > WS-ON/CUE-OFF > L,T
  205. # 65349 < 0 1 0 0 0 1 0 1 > LEDc|rt|rb (+TS-OFF) > PG-ON (CUE/GO-ON) > L,T
  206. # 65350 < 0 1 0 0 0 1 1 0 > LEDc|tl|tr (+TS-OFF) > HF-ON (CUE/GO-ON) > L,T
  207. # 65353 < 0 1 0 0 1 0 0 1 > LEDc|bl|br (+TS-OFF) > LF-ON (CUE/GO-ON) > L,T
  208. # 65354 < 0 1 0 0 1 0 1 0 > LEDc|lb|lt (+TS-OFF) > SG-ON (CUE/GO-ON) > L,T
  209. # 65359 < 0 1 0 0 1 1 1 1 > LEDall > ERROR-FLASH-ON > L,T
  210. # 65360 < 0 1 0 1 0 0 0 0 > LEDc (+TS-ON) > WS-ON/CUE-OFF > N
  211. # 65365 < 0 1 0 1 0 1 0 1 > LEDc|rt|rb (+TS-ON) > PG-ON (CUE/GO-ON) > N
  212. # 65366 < 0 1 0 1 0 1 1 0 > LEDc|tl|tr (+TS-ON) > HF-ON (CUE/GO-ON) > N
  213. # 65369 < 0 1 0 1 1 0 0 1 > LEDc|bl|br (+TS-ON) > LF-ON (CUE/GO-ON) > N
  214. # 65370 < 0 1 0 1 1 0 1 0 > LEDc|lb|lt (+TS-ON) > SG-ON (CUE/GO-ON) > N
  215. # 65376 < 0 1 1 0 0 0 0 0 > LEDc+TaSw > GO-OFF/RW-OFF > all
  216. # 65381 < 0 1 1 0 0 1 0 1 > TaSw (+LEDc|rt|rb) > SR (+PG) > all
  217. # 65382 < 0 1 1 0 0 1 1 0 > TaSw (+LEDc|tl|tr) > SR (+HF) > all
  218. # 65383 < 0 1 1 0 0 1 1 1 > TaSw (+LEDc|rt|rb|tl) > SR (+PGHF/HFPG) >
  219. # 65385 < 0 1 1 0 1 0 0 1 > TaSw (+LEDc|bl|br) > SR (+LF) > all
  220. # 65386 < 0 1 1 0 1 0 1 0 > TaSw (+LEDc|lb|lt) > SR (+SG) > all
  221. # 65387 < 0 1 1 0 1 0 1 1 > TaSw (+LEDc|lb|lt|br) > SR (+SGLF/LGSG) >
  222. # 65389 < 0 1 1 0 1 1 0 1 > TaSw (+LEDc|rt|rb|bl) > SR (+PGLF/LFPG) >
  223. # 65390 < 0 1 1 0 1 1 1 0 > TaSw (+LEDc|lb|lt|tr) > SR (+SGHF/HFSG) >
  224. # 65391 < 0 1 1 0 1 1 1 1 > LEDall (+TaSw) > ERROR-FLASH-ON > L,T
  225. # 65440 < 1 0 1 0 0 0 0 0 > RwPu (+TaSw) > RW-ON (noLEDs) > N
  226. # 65504 < 1 1 1 0 0 0 0 0 > RwPu (+LEDc) > RW-ON (-CONF) > L,T
  227. # 65509 < 1 1 1 0 0 1 0 1 > RwPu (+LEDcr) > RW-ON (+CONF-PG) > all
  228. # 65510 < 1 1 1 0 0 1 1 0 > RwPu (+LEDct) > RW-ON (+CONF-HF) > N?
  229. # 65513 < 1 1 1 0 1 0 0 1 > RwPu (+LEDcb) > RW-ON (+CONF-LF) > N?
  230. # 65514 < 1 1 1 0 1 0 1 0 > RwPu (+LEDcl) > RW-ON (+CONF-SG) > all
  231. # ^ ^ ^ ^ ^ ^ ^ ^
  232. # label binary code
  233. #
  234. # ABBREVIATIONS:
  235. # c (central), l (left), t (top), b (bottom), r (right),
  236. # HF (high force, LEDt), LF (low force, LEDb), SG (side grip, LEDl),
  237. # PG (precision grip, LEDr), RwPu (reward pump), TaSw (table switch),
  238. # TS (trial start), SR (switch release), WS (warning signal), RW (reward),
  239. # L (Lilou), T (Tanya t+a), N (Nikos n+i)
  240. ###########################################################################
  241. # Create dictionaries for event labels
  242. event_labels_str = {
  243. '65280': 'TS-OFF/STOP',
  244. '65296': 'TS-ON',
  245. '65312': 'STOP',
  246. '65344': 'WS-ON/CUE-OFF',
  247. '65349': 'PG-ON',
  248. '65350': 'HF-ON',
  249. '65353': 'LF-ON',
  250. '65354': 'SG-ON',
  251. '65359': 'ERROR-FLASH-ON',
  252. '65360': 'WS-ON/CUE-OFF',
  253. '65365': 'PG-ON',
  254. '65366': 'HF-ON',
  255. '65369': 'LF-ON',
  256. '65370': 'SG-ON',
  257. '65376': 'GO/RW-OFF',
  258. '65381': 'SR (+PG)',
  259. '65382': 'SR (+HF)',
  260. '65383': 'SR (+PGHF/HFPG)',
  261. '65385': 'SR (+LF)',
  262. '65386': 'SR (+SG)',
  263. '65387': 'SR (+SGLF/LFSG)',
  264. '65389': 'SR (+PGLF/LFPG)',
  265. '65390': 'SR (+SGHF/HFSG)',
  266. '65391': 'ERROR-FLASH-ON',
  267. '65440': 'RW-ON (noLEDs)',
  268. '65504': 'RW-ON (-CONF)',
  269. '65509': 'RW-ON (+CONF-PG)',
  270. '65510': 'RW-ON (+CONF-HF)',
  271. '65513': 'RW-ON (+CONF-LF)',
  272. '65514': 'RW-ON (+CONF-SG)'}
  273. event_labels_codes = dict(
  274. [(k, []) for k in np.unique(list(event_labels_str.values()))]) #inefficient in Python2
  275. for k in list(event_labels_codes):
  276. for l, v in event_labels_str.items(): #inefficient in Python2
  277. if v == k:
  278. event_labels_codes[k].append(l)
  279. # additional summaries
  280. event_labels_codes['CUE/GO'] = \
  281. event_labels_codes['SG-ON'] + \
  282. event_labels_codes['PG-ON'] + \
  283. event_labels_codes['LF-ON'] + \
  284. event_labels_codes['HF-ON']
  285. event_labels_codes['RW-ON'] = \
  286. event_labels_codes['RW-ON (+CONF-PG)'] + \
  287. event_labels_codes['RW-ON (+CONF-HF)'] + \
  288. event_labels_codes['RW-ON (+CONF-LF)'] + \
  289. event_labels_codes['RW-ON (+CONF-SG)'] + \
  290. event_labels_codes['RW-ON (-CONF)'] + \
  291. event_labels_codes['RW-ON (noLEDs)']
  292. event_labels_codes['SR'] = \
  293. event_labels_codes['SR (+PG)'] + \
  294. event_labels_codes['SR (+HF)'] + \
  295. event_labels_codes['SR (+LF)'] + \
  296. event_labels_codes['SR (+SG)'] + \
  297. event_labels_codes['SR (+PGHF/HFPG)'] + \
  298. event_labels_codes['SR (+SGHF/HFSG)'] + \
  299. event_labels_codes['SR (+PGLF/LFPG)'] + \
  300. event_labels_codes['SR (+SGLF/LFSG)']
  301. del k, l, v
  302. # Create dictionaries for constant trial sequences (in all monkeys)
  303. # (bit position (value) set if trial event (key) occurred)
  304. trial_const_sequence_codes = {
  305. 'TS-ON': 0,
  306. 'WS-ON': 1,
  307. 'CUE-ON': 2,
  308. 'CUE-OFF': 3,
  309. 'GO-ON': 4,
  310. 'SR': 5,
  311. 'RW-ON': 6,
  312. 'STOP': 7}
  313. trial_const_sequence_str = dict(
  314. (v, k) for k, v in trial_const_sequence_codes.items()) #inefficient in Python2
  315. # Create dictionaries for trial performances
  316. # (resulting decimal number from binary number created from trial_sequence)
  317. performance_codes = {
  318. 'incomplete_trial': 0,
  319. 'error<SR-ON': 159,
  320. 'error<WS': 161,
  321. 'error<CUE-ON': 163,
  322. 'error<CUE-OFF': 167,
  323. 'error<GO-ON': 175,
  324. 'grip_error': 191,
  325. 'correct_trial': 255}
  326. performance_str = dict((v, k) for k, v in performance_codes.items()) #inefficient in Python2
  327. def __init__(
  328. self, filename, odml_directory=None,
  329. nsx_override=None, nev_override=None,
  330. sif_override=None, ccf_override=None, odml_filename=None,
  331. verbose=False):
  332. """
  333. Constructor
  334. """
  335. # Remember choice whether to print diagnostic messages or not
  336. self._verbose = verbose
  337. # Remove known extensions from input filename
  338. for ext in self.extensions:
  339. filename = re.sub(os.path.extsep + ext + '$', '', filename)
  340. if nev_override:
  341. # check if sorting postfix is appended to nev_override name
  342. if nev_override[-3] == '-':
  343. sorting_postfix = nev_override[-2:]
  344. else:
  345. sorting_postfix = None
  346. sorting_version = nev_override
  347. else:
  348. # find most recent spike sorting version
  349. nev_versions = [re.sub(
  350. os.path.extsep + 'nev$', '', p) for p in glob.glob(
  351. filename + '*.nev')]
  352. nev_versions = [p.replace(filename, '') for p in nev_versions]
  353. if len(nev_versions):
  354. sorting_postfix = sorted(nev_versions)[-1]
  355. else:
  356. sorting_postfix = ''
  357. sorting_version = filename + sorting_postfix
  358. # Initialize file
  359. BlackrockIO.__init__(
  360. self, filename, nsx_override=nsx_override,
  361. nev_override=sorting_version, sif_override=sif_override,
  362. ccf_override=ccf_override, verbose=verbose)
  363. # if no odML directory is specified, use same directory as main files
  364. if not odml_directory:
  365. odml_directory = os.path.dirname(self.filename)[:-1]
  366. # remove extensions from odml override
  367. filen = os.path.split(self.filename)[-1]
  368. if odml_filename:
  369. self._filenames['odml'] = ''.join(
  370. [odml_directory, os.path.sep, odml_filename])
  371. else:
  372. self._filenames['odml'] = ''.join(
  373. [odml_directory, os.path.sep, filen])
  374. file2check = ''.join([self._filenames['odml'], os.path.extsep, 'odml'])
  375. if os.path.exists(file2check):
  376. self._avail_files['odml'] = True
  377. self.odmldoc = odml.tools.xmlparser.load(file2check)
  378. else:
  379. self._avail_files['odml'] = False
  380. self.odmldoc = None
  381. # If we did not specify an explicit sorting version, and there is an
  382. # odML, then make sure the detected sorting version matches the odML
  383. if self.odmldoc:
  384. if self.odmldoc.sections['PreProcessing'].sections[
  385. 'OfflineSpikeSorting'].properties[
  386. 'Sortings'].value.data != sorting_postfix:
  387. self._print_verbose(
  388. "Attempting to utilize the most recent "
  389. "sorting version in file %s, but the sorting version "
  390. "specified in odML is %s" % (
  391. sorting_version,
  392. self.odmldoc.sections['PreProcessing'].sections[
  393. 'OfflineSpikeSorting'].properties['Sortings']))
  394. self._load_spikesorting_info = False
  395. else:
  396. self._load_spikesorting_info = True
  397. else:
  398. self._load_spikesorting_info = False
  399. def __is_set(self, flag, pos):
  400. """
  401. Checks if bit is set at the given position for flag. If flag is an
  402. array, an array will be returned.
  403. """
  404. return flag & (1 << pos) > 0
  405. def __set_bit(self, flag, pos):
  406. """
  407. Returns the given flag with an additional bit set at the given
  408. position. for flag. If flag is an array, an array will be returned.
  409. """
  410. return flag | (1 << pos)
  411. def __add_rejection_to_event(self, event):
  412. """
  413. Given an event with annotation trial_id, adds information on whether to
  414. reject the trial or not.
  415. """
  416. if self.odmldoc:
  417. # Get rejection bands
  418. sec = self.odmldoc['PreProcessing']
  419. bands = sec.properties['LFPBands'].value
  420. for band in bands:
  421. sec = self.odmldoc['PreProcessing'][band.data]
  422. if type(sec.properties['RejTrials'].value) is list:
  423. rej_trials = [int(_.data) for _ in sec.properties[
  424. 'RejTrials'].value]
  425. rej_index = np.in1d(
  426. event.annotations['trial_id'],
  427. rej_trials)
  428. elif sec.properties['RejTrials'].value.data == -1:
  429. rej_index = np.zeros(
  430. (len(event.annotations['trial_id'])), dtype=bool)
  431. elif sec.properties['RejTrials'].value.data >= 0:
  432. rej_index = np.in1d(
  433. event.annotations['trial_id'],
  434. [sec.properties['RejTrials'].value])
  435. else:
  436. raise ValueError(
  437. "Invalid entry %s in odML for rejected trials in LFP "
  438. " band %s." %
  439. (sec.properties['RejTrials'].value.data, band.data))
  440. event.annotate(
  441. **{str('trial_reject_' + band.data): list(rej_index)})
  442. def __extract_task_condition(self, trialtypes):
  443. """
  444. Extracts task condition from trialtypes.
  445. """
  446. occurring_trtys = np.unique(trialtypes).tolist()
  447. # reduce occurring_trtys to actual trialtypes
  448. # (remove all not identifiable trialtypes (incomplete/error trial))
  449. if 'NONE' in occurring_trtys:
  450. occurring_trtys.remove('NONE')
  451. # (remove all trialtypes where only the CUE was detected (error trial))
  452. if 'SG' in occurring_trtys:
  453. occurring_trtys.remove('SG')
  454. if 'PG' in occurring_trtys:
  455. occurring_trtys.remove('PG')
  456. if 'LF' in occurring_trtys:
  457. occurring_trtys.remove('LF')
  458. if 'HF' in occurring_trtys:
  459. occurring_trtys.remove('HF')
  460. # first set to unidentified task condition
  461. task_condition = 0
  462. if len(occurring_trtys) > 0:
  463. for cnd, trtys in self.condition_str.items(): #inefficient in Python2
  464. if set(trtys) == set(occurring_trtys):
  465. # replace with detected task condition
  466. task_condition = cnd
  467. return task_condition
  468. def __extract_analog_events_from_odml(self, t_start, t_stop):
  469. event_name = []
  470. event_time = []
  471. trial_id = []
  472. trial_timestamp_id = []
  473. performance_code = []
  474. trial_type = []
  475. # Look for all Trial Sections
  476. sec = self.odmldoc['Recording']['TaskSettings']
  477. ff = lambda x: x.name.startswith('Trial_')
  478. tr_secs = sec.itersections(filter_func=ff)
  479. for trial_sec in tr_secs:
  480. for signalname in ['GripForceSignals', 'DisplacementSignal']:
  481. for analog_events in trial_sec[
  482. 'AnalogEvents'][signalname].properties:
  483. time = analog_events.value.data * \
  484. pq.CompoundUnit(analog_events.value.unit)
  485. if time >= t_start and time < t_stop:
  486. event_name.append(analog_events.name)
  487. event_time.append(time)
  488. trial_id.append(
  489. trial_sec.properties['TrialID'].value.data)
  490. trial_timestamp_id.append(
  491. trial_sec.properties[
  492. 'TrialTimestampID'].value.data)
  493. performance_code.append(
  494. trial_sec.properties['PerformanceCode'].value.data)
  495. trial_type.append(
  496. trial_sec.properties['TrialType'].value.data)
  497. # Create event object with analog events
  498. analog_events = neo.Event(
  499. times=pq.Quantity(
  500. [_.magnitude for _ in event_time],
  501. units=event_time[0].units).rescale('ms'),
  502. labels=np.array(event_name),
  503. name='AnalogTrialEvents',
  504. description='Events extracted from analog signals')
  505. analog_events.annotate(
  506. trial_id=trial_id,
  507. trial_timestamp_id=trial_timestamp_id,
  508. performance_in_trial=performance_code,
  509. belongs_to_trialtype=trial_type,
  510. trial_event_labels=event_name)
  511. return analog_events
  512. def __annotate_dig_trial_events(self, events):
  513. """
  514. Modifies events of digital input port to trial events of the
  515. reach-to-grasp project.
  516. """
  517. # Modifiy name and description
  518. events.name = "DigitalTrialEvents"
  519. events.description = "Trial " + events.description.lower()
  520. # Uncomment for event and trial sequence debugging
  521. # for ev in events.labels:
  522. # if ev in list(self.event_labels_str):
  523. # print ev, self.event_labels_str[ev]
  524. # else:
  525. # print ev
  526. # Extract beginning of first complete trial
  527. tson_label = self.event_labels_codes['TS-ON'][0]
  528. if tson_label in events.labels:
  529. first_TSon_idx = list(events.labels).index(tson_label)
  530. else:
  531. first_TSon_idx = len(events.labels)
  532. # Extract end of last complete trial
  533. stop_label = self.event_labels_codes['STOP'][0]
  534. if stop_label in events.labels:
  535. last_WSoff_idx = len(events.labels) - \
  536. list(events.labels[::-1]).index(stop_label) - 1
  537. else:
  538. last_WSoff_idx = -1
  539. # Annotate events with modified labels, trial ids, and trial types
  540. trial_event_labels = []
  541. trial_ID = []
  542. trial_timestamp_ID = []
  543. trialtypes = {-1: 'NONE'}
  544. trialsequence = {-1: 0}
  545. for i, l in enumerate(events.labels):
  546. if i < first_TSon_idx or i > last_WSoff_idx:
  547. trial_event_labels.append('NONE')
  548. trial_ID.append(-1)
  549. trial_timestamp_ID.append(-1)
  550. else:
  551. # interpretation of TS-ON
  552. if self.event_labels_str[l] == 'TS-ON':
  553. if i > 0:
  554. prev_ev = events.labels[i - 1]
  555. if self.event_labels_str[prev_ev] in \
  556. ['STOP', 'TS-OFF/STOP']:
  557. timestamp_id = int(events.times[i].rescale(
  558. self._BlackrockIO__nev_params(
  559. 'event_unit')).item())
  560. trial_timestamp_ID.append(timestamp_id)
  561. trial_event_labels.append('TS-ON')
  562. trialsequence[timestamp_id] = self.__set_bit(
  563. 0, self.trial_const_sequence_codes['TS-ON'])
  564. else:
  565. timestamp_id = trial_timestamp_ID[-1]
  566. trial_timestamp_ID.append(timestamp_id)
  567. trial_event_labels.append('TS-ON-ERROR')
  568. else:
  569. timestamp_id = int(events.times[i].rescale(
  570. self._BlackrockIO__nev_params(
  571. 'event_unit')).item())
  572. trial_timestamp_ID.append(timestamp_id)
  573. trial_event_labels.append('TS-ON')
  574. trialsequence[timestamp_id] = self.__set_bit(
  575. 0, self.trial_const_sequence_codes['TS-ON'])
  576. # Identify trial ID if odML exists
  577. ID = -1
  578. if self.odmldoc:
  579. sec = self.odmldoc['Recording']['TaskSettings']
  580. ff = lambda x: x.name.startswith('Trial_')
  581. tr_secs = sec.itersections(filter_func=ff)
  582. for trial_sec in tr_secs:
  583. if trial_sec.properties[
  584. 'TrialTimestampID'].value.data == \
  585. timestamp_id:
  586. ID = trial_sec.properties['TrialID'].value.data
  587. trial_ID.append(ID)
  588. # interpretation of GO/RW-OFF
  589. elif self.event_labels_str[l] == 'GO/RW-OFF':
  590. trial_timestamp_ID.append(timestamp_id)
  591. trial_ID.append(ID)
  592. trial_event_labels.append('GO/RW-OFF')
  593. # interpretation of ERROR-FLASH-ON
  594. elif l in self.event_labels_codes['ERROR-FLASH-ON']:
  595. trial_timestamp_ID.append(timestamp_id)
  596. trial_ID.append(ID)
  597. trial_event_labels.append('ERROR-FLASH-ON')
  598. # Error-Flash hides too early activation of SR
  599. # SR is set to 1 here to match perf codes between monkeys
  600. trialsequence[timestamp_id] = self.__set_bit(
  601. trialsequence[timestamp_id],
  602. self.trial_const_sequence_codes['SR'])
  603. # TS-OFF/STOP
  604. elif self.event_labels_str[l] == 'TS-OFF/STOP':
  605. trial_timestamp_ID.append(timestamp_id)
  606. trial_ID.append(ID)
  607. prev_ev = events.labels[i - 1]
  608. if self.event_labels_str[prev_ev] == 'TS-ON':
  609. trial_event_labels.append('TS-OFF')
  610. elif prev_ev in self.event_labels_codes['ERROR-FLASH-ON']:
  611. trial_event_labels.append('STOP')
  612. trialsequence[timestamp_id] = self.__set_bit(
  613. trialsequence[timestamp_id],
  614. self.trial_const_sequence_codes['STOP'])
  615. else:
  616. trial_event_labels.append('STOP')
  617. trialsequence[timestamp_id] = self.__set_bit(
  618. trialsequence[timestamp_id],
  619. self.trial_const_sequence_codes['STOP'])
  620. # interpretation of WS-ON/CUE-OFF
  621. elif self.event_labels_str[l] == 'WS-ON/CUE-OFF':
  622. trial_timestamp_ID.append(timestamp_id)
  623. trial_ID.append(ID)
  624. prev_ev = events.labels[i - 1]
  625. if self.event_labels_str[prev_ev] in \
  626. ['TS-ON', 'TS-OFF/STOP']:
  627. trial_event_labels.append('WS-ON')
  628. trialsequence[timestamp_id] = self.__set_bit(
  629. trialsequence[timestamp_id],
  630. self.trial_const_sequence_codes['WS-ON'])
  631. elif (prev_ev in self.event_labels_codes['CUE/GO'] or
  632. prev_ev in self.event_labels_codes['GO/RW-OFF']):
  633. trial_event_labels.append('CUE-OFF')
  634. trialsequence[timestamp_id] = self.__set_bit(
  635. trialsequence[timestamp_id],
  636. self.trial_const_sequence_codes['CUE-OFF'])
  637. else:
  638. raise ValueError("Unknown trial event sequence.")
  639. # interpretation of CUE and GO events and trialtype detection
  640. elif l in self.event_labels_codes['CUE/GO']:
  641. trial_timestamp_ID.append(timestamp_id)
  642. trial_ID.append(ID)
  643. prprev_ev = events.labels[i - 2]
  644. if self.event_labels_str[prprev_ev] in \
  645. ['TS-ON', 'TS-OFF/STOP']:
  646. trial_event_labels.append('CUE-ON')
  647. trialsequence[timestamp_id] = self.__set_bit(
  648. trialsequence[timestamp_id],
  649. self.trial_const_sequence_codes['CUE-ON'])
  650. trialtypes[timestamp_id] = self.event_labels_str[l][:2]
  651. elif prprev_ev in self.event_labels_codes['CUE/GO']:
  652. trial_event_labels.append('GO-ON')
  653. trialsequence[timestamp_id] = self.__set_bit(
  654. trialsequence[timestamp_id],
  655. self.trial_const_sequence_codes['GO-ON'])
  656. trialtypes[timestamp_id] += \
  657. self.event_labels_str[l][:2]
  658. else:
  659. raise ValueError("Unknown trial event sequence.")
  660. # interpretation of WS-OFF
  661. elif self.event_labels_str[l] == 'STOP':
  662. trial_timestamp_ID.append(timestamp_id)
  663. trial_ID.append(ID)
  664. prev_ev = self.event_labels_str[events.labels[i - 1]]
  665. if prev_ev == 'ERROR-FLASH-ON':
  666. trial_event_labels.append('ERROR-FLASH-OFF')
  667. else:
  668. trial_event_labels.append('STOP')
  669. trialsequence[timestamp_id] = self.__set_bit(
  670. trialsequence[timestamp_id],
  671. self.trial_const_sequence_codes['STOP'])
  672. # interpretation of SR events
  673. elif l in self.event_labels_codes['SR']:
  674. trial_timestamp_ID.append(timestamp_id)
  675. trial_ID.append(ID)
  676. prev_ev = events.labels[i - 1]
  677. if prev_ev in self.event_labels_codes['SR']:
  678. trial_event_labels.append('SR-REP')
  679. elif prev_ev in self.event_labels_codes['RW-ON']:
  680. trial_event_labels.append('RW-OFF')
  681. else:
  682. trial_event_labels.append('SR')
  683. trialsequence[timestamp_id] = self.__set_bit(
  684. trialsequence[timestamp_id],
  685. self.trial_const_sequence_codes['SR'])
  686. # interpretation of RW events
  687. elif l in self.event_labels_codes['RW-ON']:
  688. trial_timestamp_ID.append(timestamp_id)
  689. trial_ID.append(ID)
  690. prev_ev = events.labels[i - 1]
  691. if prev_ev in self.event_labels_codes['RW-ON']:
  692. trial_event_labels.append('RW-ON-REP')
  693. else:
  694. trial_event_labels.append('RW-ON')
  695. trialsequence[timestamp_id] = self.__set_bit(
  696. trialsequence[timestamp_id],
  697. self.trial_const_sequence_codes['RW-ON'])
  698. else:
  699. raise ValueError("Unknown event label.")
  700. # add modified trial_event_labels to annotations
  701. events.annotate(trial_event_labels=trial_event_labels)
  702. # add trial timestamp IDs
  703. events.annotate(trial_timestamp_id=trial_timestamp_ID)
  704. # add trial IDs
  705. events.annotate(trial_id=trial_ID)
  706. # add modified belongs_to_trialtype to annotations
  707. for tid in trial_timestamp_ID:
  708. if tid not in list(trialtypes):
  709. trialtypes[tid] = 'NONE'
  710. belongs_to_trialtype = [
  711. trialtypes[tid] for tid in trial_timestamp_ID]
  712. events.annotate(belongs_to_trialtype=belongs_to_trialtype)
  713. # add modified trial_performance_codes to annotations
  714. performance_in_trial = [
  715. trialsequence[tid] for tid in trial_timestamp_ID]
  716. events.annotate(performance_in_trial=performance_in_trial)
  717. def __annotate_units_with_odml(self, units):
  718. """
  719. Annotates units with metadata from odml file.
  720. """
  721. # Can the spike sorting info from the odML be matched with the odML?
  722. if not self._load_spikesorting_info:
  723. return
  724. for un in units:
  725. an_dict = dict(
  726. sua=False,
  727. mua=False,
  728. noise=False)
  729. try:
  730. sec = self.odmldoc['UtahArray']['Array'][
  731. 'Electrode_%03d' % un.annotations['channel_id']][
  732. 'OfflineSpikeSorting']
  733. except KeyError:
  734. return
  735. suaids = [v.data for v in sec.properties['SUAIDs'].values]
  736. muaid = sec.properties['MUAID'].value.data
  737. noiseids = [v.data for v in sec.properties['NoiseIDs'].values]
  738. if un.annotations['unit_id'] in suaids:
  739. an_dict['sua'] = True
  740. elif un.annotations['unit_id'] in noiseids:
  741. an_dict['noise'] = True
  742. elif un.annotations['unit_id'] == muaid:
  743. an_dict['mua'] = True
  744. else:
  745. raise ValueError(
  746. "Unit %i is not registered for channel %i in odML file."
  747. % (un.annotations['unit_id'],
  748. un.annotations['channel_id']))
  749. if ('Unit_%02i' % un.annotations['unit_id']) in sec.sections:
  750. unit_sec = sec['Unit_%02i' % un.annotations['unit_id']]
  751. if an_dict['sua']:
  752. an_dict['SNR'] = unit_sec.properties['SNR'].value.data
  753. an_dict['spike_duration'] = unit_sec.properties[
  754. 'SpikeDuration'].value.data
  755. an_dict['spike_amplitude'] = unit_sec.properties[
  756. 'SpikeAmplitude'].value.data
  757. an_dict['spike_count'] = unit_sec.properties[
  758. 'SpikeCount'].value.data
  759. # Annotate Unit and all children for convenience
  760. un.annotate(**an_dict)
  761. for st in un.spiketrains:
  762. st.annotate(**an_dict)
  763. def __annotate_spiketrains_with_odml(self, sts):
  764. """
  765. Annotates spiketrains with metadata from odml file.
  766. """
  767. def __annotate_analogsignals_with_odml(self, asig):
  768. """
  769. Annotates analogsignals with metadata from odml file.
  770. """
  771. if self.odmldoc and asig.annotations['channel_id'] in range(1, 129):
  772. # Annotate filter settings from odML
  773. sec = self.odmldoc[
  774. 'Cerebus']['NeuralSignalProcessor']['NeuralSignals'][
  775. 'Filter_ns%i' % asig.annotations['nsx']]
  776. asig.annotate(
  777. filter_hi_pass_freq=pq.Quantity(
  778. sec.properties['HighPassFreq'].value.data,
  779. sec.properties['HighPassFreq'].value.unit),
  780. filter_lo_pass_freq=pq.Quantity(
  781. sec.properties['LowPassFreq'].value.data,
  782. sec.properties['LowPassFreq'].value.unit),
  783. filter_hi_pass_order=sec.properties[
  784. 'HighPassOrder'].value.data,
  785. filter_lo_pass_order=sec.properties[
  786. 'LowPassOrder'].value.data,
  787. filter_type=sec.properties[
  788. 'Type'].value.data)
  789. def __annotate_channelindex_with_odml(self, chidx):
  790. """
  791. Annotates channelindex with metadata from odml file.
  792. """
  793. if self.odmldoc:
  794. # Get rejection bands
  795. sec = self.odmldoc['PreProcessing']
  796. bands = sec.properties['LFPBands'].value
  797. if hasattr(bands, '__iter__'):
  798. for band in bands:
  799. sec = self.odmldoc['PreProcessing'][band.data]
  800. if type(sec.properties['RejElectrodes'].value) is list:
  801. rej_electrodes = [int(_.data) for _ in sec.properties[
  802. 'RejElectrodes'].value]
  803. rej = chidx.channel_ids[0] in rej_electrodes
  804. elif sec.properties['RejElectrodes'].value.data == -1:
  805. rej = False
  806. elif sec.properties['RejElectrodes'].value.data >= 0:
  807. rej_electrodes = sec.properties[
  808. 'RejElectrodes'].value.data
  809. rej = (chidx.channel_ids[0] == rej_electrodes)
  810. else:
  811. raise ValueError(
  812. "Invalid entry %s in odML for rejected electrodes "
  813. "in LFP band %s." % (
  814. sec.properties['RejElectrodes'].value.data,
  815. band.data))
  816. rej_dict = {str('electrode_reject_' + band.data): rej}
  817. # Annotate ChannelIndex and all children for convenience
  818. chidx.annotate(**rej_dict)
  819. for asig in chidx.analogsignals:
  820. asig.annotate(**rej_dict)
  821. for unit in chidx.units:
  822. unit.annotate(**rej_dict)
  823. for st in unit.spiketrains:
  824. st.annotate(**rej_dict)
  825. # Annotate connector aligned ID to channel
  826. if chidx.channel_ids[0] in \
  827. chidx.block.annotations['avail_electrode_ids']:
  828. ca_dict = {
  829. 'connector_aligned_id': chidx.block.annotations[
  830. 'avail_electrode_ids'].index(chidx.channel_ids[0])+1}
  831. chidx.coordinates = pq.Quantity(np.array([
  832. np.mod(ca_dict['connector_aligned_id']-1, 10)*.4,
  833. (ca_dict['connector_aligned_id']-1)/10*.4]),
  834. units=pq.mm)
  835. chidx.annotate(**ca_dict)
  836. for asig in chidx.analogsignals:
  837. asig.annotate(**ca_dict)
  838. for unit in chidx.units:
  839. unit.annotate(**ca_dict)
  840. for st in unit.spiketrains:
  841. st.annotate(**ca_dict)
  842. def __annotate_block_with_odml(self, bl):
  843. """
  844. Annotates block with metadata from odml file.
  845. """
  846. sec = self.odmldoc['Project']
  847. bl.annotate(
  848. project_name=sec.properties['Name'].value.data,
  849. project_type=sec.properties['Type'].value.data,
  850. project_subtype=sec.properties['Subtype'].value.data)
  851. sec = self.odmldoc['Project']['TaskDesigns']
  852. bl.annotate(
  853. taskdesigns=[v.data for v in sec.properties['UsedDesign'].values])
  854. sec = self.odmldoc['Subject']
  855. bl.annotate(
  856. subject_name=sec.properties['GivenName'].value.data,
  857. subject_gender=sec.properties['Gender'].value.data,
  858. subject_activehand=sec.properties['ActiveHand'].value.data,
  859. subject_birthday=sec.properties['Birthday'].value.data)
  860. sec = self.odmldoc['Setup']
  861. bl.annotate(setup_location=sec.properties['Location'].value.data)
  862. sec = self.odmldoc['UtahArray']
  863. bl.annotate(array_serialnum=sec.properties['SerialNo'].value.data)
  864. sec = self.odmldoc['UtahArray']['Connector']
  865. bl.annotate(connector_type=sec.properties['Style'].value.data)
  866. sec = self.odmldoc['UtahArray']['Array']
  867. bl.annotate(arraygrids_tot_num=sec.properties['GridCount'].value.data)
  868. sec = self.odmldoc['UtahArray']['Array']['Grid_01']
  869. bl.annotate(
  870. electrodes_tot_num=sec.properties['ElectrodeCount'].value.data,
  871. electrodes_pitch=pq.Quantity(
  872. sec.properties['ElectrodePitch'].value.data,
  873. units=sec.properties['ElectrodePitch'].value.unit),
  874. arraygrid_row_num=sec.properties['GridRows'].value.data,
  875. arraygrid_col_num=sec.properties['GridColumns'].value.data)
  876. secs = self.odmldoc['UtahArray']['Array'].sections
  877. bl.annotate(
  878. avail_electrode_ids=[])
  879. for i in range(1, 101):
  880. elidx = [s.properties['ID'].value.data for s in secs if
  881. s.name.startswith('Electrode') and
  882. s.properties['ConnectorAlignedID'].value.data == i]
  883. if len(elidx) == 0:
  884. bl.annotations['avail_electrode_ids'].append(-1)
  885. elif len(elidx) == 1:
  886. bl.annotations['avail_electrode_ids'].append(elidx[0])
  887. else:
  888. raise ValueError("Electrode IDs in odML file are corrupt. "
  889. "ID %i occurs %i times" % (i, len(elidx)))
  890. # TODO: add list of behavioral channels
  891. # bl.annotate(avail_behavsig_indexes=[])
  892. def __correct_filter_shifts(self, asig):
  893. if self.odmldoc and asig.annotations['channel_id'] in range(1, 129):
  894. # Get and correct for shifts
  895. sec = self.odmldoc[
  896. 'Cerebus']['NeuralSignalProcessor']['NeuralSignals'][
  897. 'Filter_ns%i' % asig.annotations['nsx']]
  898. shift = pq.Quantity(
  899. sec.properties['EstimatedShift'].value.data,
  900. sec.properties['EstimatedShift'].value.unit)
  901. asig.t_start = asig.t_start - shift
  902. # Annotate shift
  903. asig.annotate(filter_shift_correction=shift)
  904. def __merge_digital_analog_events(self, events):
  905. """
  906. Merge the two event arrays AnalogTrialEvents and DigitalTrialEvents
  907. into one common event array TrialEvents.
  908. """
  909. event_name = []
  910. event_time = None
  911. trial_id = []
  912. trial_timestamp_id = []
  913. performance_code = []
  914. trial_type = []
  915. for event in events:
  916. if event.name in ['AnalogTrialEvents', 'DigitalTrialEvents']:
  917. # Extract event times
  918. if event_time is None:
  919. event_time = event.times.magnitude
  920. event_units = event.times.units
  921. else:
  922. event_time = np.concatenate((
  923. event_time,
  924. event.times.rescale(event_units).magnitude))
  925. # Transfer annotations
  926. trial_id.extend(
  927. event.annotations['trial_id'])
  928. trial_timestamp_id.extend(
  929. event.annotations['trial_timestamp_id'])
  930. performance_code.extend(
  931. event.annotations['performance_in_trial'])
  932. trial_type.extend(
  933. event.annotations['belongs_to_trialtype'])
  934. event_name.extend(
  935. event.annotations['trial_event_labels'])
  936. # Sort time stamps and save sort order
  937. sort_idx = np.argsort(event_time)
  938. event_time = event_time[sort_idx]
  939. # Create event object with analog events
  940. merged_event = neo.Event(
  941. times=pq.Quantity(event_time, units=event_units),
  942. labels=np.array([event_name[_] for _ in sort_idx]),
  943. name='TrialEvents',
  944. description='All trial events (digital and analog)')
  945. merged_event.annotate(
  946. trial_id=[trial_id[_] for _ in sort_idx],
  947. trial_timestamp_id=[trial_timestamp_id[_] for _ in sort_idx],
  948. performance_in_trial=[performance_code[_] for _ in sort_idx],
  949. belongs_to_trialtype=[trial_type[_] for _ in sort_idx],
  950. trial_event_labels=[event_name[_] for _ in sort_idx])
  951. return merged_event
  952. def read_block(
  953. self, index=None, name=None, description=None, nsx_to_load='none',
  954. n_starts=None, n_stops=None, channels=range(1, 97), units='none',
  955. load_waveforms=False, load_events=False, scaling='raw',
  956. correct_filter_shifts=True, lazy=False, cascade=True):
  957. """
  958. Reads file contents as a Neo Block.
  959. The Block contains one Segment for each entry in zip(n_starts,
  960. n_stops). If these parameters are not specified, the default is
  961. to store all data in one Segment.
  962. The Block contains one ChannelIndex per channel.
  963. Args:
  964. index (None, int):
  965. If not None, index of block is set to user input.
  966. name (None, str):
  967. If None, name is set to default, otherwise it is set to user
  968. input.
  969. description (None, str):
  970. If None, description is set to default, otherwise it is set to
  971. user input.
  972. nsx_to_load (int, list, str):
  973. ID(s) of nsx file(s) from which to load data, e.g., if set to
  974. 5 only data from the ns5 file are loaded. If 'none' or empty
  975. list, no nsx files and therefore no analog signals are loaded.
  976. If 'all', data from all available nsx are loaded.
  977. n_starts (None, Quantity, list):
  978. Start times for data in each segment. Number of entries must be
  979. equal to length of n_stops. If None, intrinsic recording start
  980. times of files set are used.
  981. n_stops (None, Quantity, list):
  982. Stop times for data in each segment. Number of entries must be
  983. equal to length of n_starts. If None, intrinsic recording stop
  984. times of files set are used.
  985. channels (int, list, str):
  986. Channel id(s) from which to load data. If 'none' or empty list,
  987. no channels and therefore no analog signal or spiketrains are
  988. loaded. If 'all', all available channels are loaded. By
  989. default, all neural channels (1-96) are loaded.
  990. units (int, list, str, dict):
  991. ID(s) of unit(s) to load. If 'none' or empty list, no units and
  992. therefore no spiketrains are loaded. If 'all', all available
  993. units are loaded. If dict, the above can be specified
  994. individually for each channel (keys), e.g. {1: 5, 2: 'all'}
  995. loads unit 5 from channel 1 and all units from channel 2.
  996. load_waveforms (boolean):
  997. If True, waveforms are attached to all loaded spiketrains.
  998. load_events (boolean):
  999. If True, all recorded events are loaded.
  1000. scaling (str):
  1001. Determines whether time series of individual
  1002. electrodes/channels are returned as AnalogSignals containing
  1003. raw integer samples ('raw'), or scaled to arrays of floats
  1004. representing voltage ('voltage'). Note that for file
  1005. specification 2.1 and lower, the option 'voltage' requires a
  1006. nev file to be present.
  1007. correct_filter_shifts (bool):
  1008. If True, shifts of the online-filtered neural signals (e.g.,
  1009. ns2, channels 1-128) are corrected by time-shifting the signal
  1010. by a heuristically determined estimate stored in the metadata,
  1011. in the property EstimatedShift, under the path
  1012. /Cerebus/NeuralSignalProcessor/NeuralSignals/Filter_nsX/
  1013. lazy (bool):
  1014. If True, only the shape of the data is loaded.
  1015. cascade (bool or "lazy"):
  1016. If True, only the block without children is returned.
  1017. Returns:
  1018. Block (neo.segment.Block):
  1019. Block linking to all loaded Neo objects.
  1020. Block annotations:
  1021. avail_file_set (list of str):
  1022. List of file extensions of the files found to be
  1023. associated to the project, and which are used in
  1024. loading the data, e.g., ccf, odml, nev, ns2,...
  1025. avail_nsx (list of int):
  1026. List of integers specifying the .nsX files available,
  1027. e.g., [2, 5] indicates that an ns2 and and ns5 file are
  1028. available.
  1029. avail_nev (bool):
  1030. True if a .nev file is available.
  1031. avail_ccf (bool):
  1032. True if a .ccf file is available.
  1033. avail_sif (bool):
  1034. True if a .sif file is available.
  1035. nb_segments (int):
  1036. Number of segments created after merging recording
  1037. times specified by user with the intrinsic ones of the
  1038. file set.
  1039. project_name (str):
  1040. Identifier for the project/experiment.
  1041. project_type (str):
  1042. Identifier for the type of project/experiment.
  1043. project_subtype (str):
  1044. Identifier of the subtype of the project/experiment.
  1045. taskdesigns (list of str):
  1046. List of strings identifying the task designed presented
  1047. during the recording. The standard task reach-to-grasp
  1048. is denoted by the string "TwoCues".
  1049. conditions (list of int):
  1050. List of condition codes (each code describing the set
  1051. of trial types presented to the subject during a
  1052. segment of the recording) present during the recording.
  1053. For a mapping of condition codes to trial types, see
  1054. the condition_str attribute of the ReachGraspIO class.
  1055. subject_name (str):
  1056. Name of the recorded subject.
  1057. subject_gender (bool):
  1058. 'male' or 'female'.
  1059. subject_birthday (datetime):
  1060. Birthday of the recorded subject.
  1061. subject_activehand (str):
  1062. Handedness of the subject.
  1063. setup_location (str):
  1064. Physical location of the recording setup.
  1065. avail_electrode_ids (list of int):
  1066. List of length 100 of electrode channel IDs (Blackrock
  1067. IDs) ordered corresponding to the connector-aligned
  1068. linear electrode IDs. The connector-aligned IDs start
  1069. at 1 in the bottom left corner, and increase from left
  1070. to right, and from bottom to top assuming the array is
  1071. placed in front of the observer pins facing down,
  1072. connector extruding to the right:
  1073. 91 92 ... 99 100 \
  1074. 81 82 ... 89 90 \
  1075. ... ... --- Connector Wires
  1076. 11 12 ... 19 20 /
  1077. 1 2 ... 9 10 /
  1078. Thus,
  1079. avail_electrode_ids[k-1]
  1080. is the Blackrock channel ID corresponding to connector-
  1081. aligned ID k. Unconnected/unavailable channels are
  1082. marked by -1.
  1083. arraygrids_tot_num (int):
  1084. Number of Utah arrays (not necessarily all connected).
  1085. electrodes_tot_num (int):
  1086. Number of electrodes of the Utah array (not necessarily
  1087. all connected).
  1088. electrodes_pitch (float):
  1089. Distance in micrometers between neighboring electrodes
  1090. in one row/column.
  1091. array_serial_num (str):
  1092. Serial number of the recording array.
  1093. array_grid_col_num, array_grid_row_num (int):
  1094. Number of columns / rows of the array.
  1095. connector_type (str):
  1096. Type of connector used for recording.
  1097. rec_pauses (bool):
  1098. True if the session contains a recording pause (i.e.,
  1099. multiple segments).
  1100. Segment annotations:
  1101. condition (int):
  1102. Condition code (describing the set of trial types
  1103. presented to the subject) of this segment. For a
  1104. mapping of condition codes to trial types, see the
  1105. condition_str attribute of the ReachGraspIO class.
  1106. ChannelIndex annotations:
  1107. connector_aligned_id (int):
  1108. Connector-aligned channel ID from which the spikes were
  1109. loaded. This is a channel ID between 1 and 100 that is
  1110. related to the location of an electrode on the Utah
  1111. array and thus common across different arrays
  1112. (independent of the Blackrock channel ID). The ID
  1113. considers a top-view of the array with the connector
  1114. wires extruding to the right. Electrodes are then
  1115. numbered from bottom left to top right:
  1116. 91 92 ... 99 100 \
  1117. 81 82 ... 89 90 \
  1118. ... ... --- Connector Wires
  1119. 11 12 ... 19 20 /
  1120. 1 2 ... 9 10 /
  1121. Note: The Blackrock IDs are given in the 'channel_ids'
  1122. property of the ChannelIndex object.
  1123. waveform_size (Quantitiy):
  1124. Length of time used to save spike waveforms (in units
  1125. of 1/30000 s).
  1126. nev_hi_freq_corner (Quantitiy),
  1127. nev_lo_freq_corner (Quantitiy),
  1128. nev_hi_freq_order (int), nev_lo_freq_order (int),
  1129. nev_hi_freq_type (str), nev_lo_freq_type (str),
  1130. nev_hi_threshold, nev_lo_threshold,
  1131. nev_energy_threshold (Quantity):
  1132. Indicates parameters of spike detection.
  1133. nev_dig_factor (int):
  1134. Digitization factor in microvolts of the nev file, used
  1135. to convert raw samples to volt.
  1136. connector_ID, connector_pinID (int):
  1137. ID of connector and pin on the connector where the
  1138. channel was recorded from.
  1139. nb_sorted_units (int):
  1140. Number of sorted units on this channel (noise, mua and
  1141. sua).
  1142. electrode_reject_XXX (bool):
  1143. For different filter ranges XXX (as defined in the odML
  1144. file), if this variable is True it indicates whether
  1145. the spikes were recorded on an electrode that should be
  1146. rejected based on preprocessing analysis for removing
  1147. electrodes due to noise/artefacts in the respective
  1148. frequency range.
  1149. Unit annotations:
  1150. coordinates (Quantity):
  1151. Contains the x and y coordinate of the electrode in mm
  1152. (spacing: 0.4mm). The coordinates use the same
  1153. representation as the connector_aligned_id with the
  1154. origin located at the bottom left electrode. Thus,
  1155. e.g., connector aligned ID 14 is at coordinates:
  1156. (1.2 mm, 0.4 mm)
  1157. unit_id (int):
  1158. ID of the unit.
  1159. channel_id (int):
  1160. Channel ID (Blackrock ID) from which the unit was
  1161. loaded (equiv. to the single list entry in the
  1162. attribute channel_ids of ChannelIndex parent).
  1163. connector_aligned_id (int):
  1164. Connector-aligned channel ID from which the unit was
  1165. loaded. This is a channel ID between 1 and 100 that is
  1166. related to the location of an electrode on the Utah
  1167. array and thus common across different arrays
  1168. (independent of the Blackrock channel ID). The ID
  1169. considers a top-view of the array with the connector
  1170. wires extruding to the right. Electrodes are then
  1171. numbered from bottom left to top right:
  1172. 91 92 ... 99 100 \
  1173. 81 82 ... 89 90 \
  1174. ... ... --- Connector Wires
  1175. 11 12 ... 19 20 /
  1176. 1 2 ... 9 10 /
  1177. electrode_reject_XXX (bool):
  1178. For different filter ranges XXX (as defined in the odML
  1179. file), if this variable is True it indicates whether
  1180. the spikes were recorded on an electrode that should be
  1181. rejected based on preprocessing analysis for removing
  1182. electrodes due to noise/artefacts in the respective
  1183. frequency range.
  1184. noise, mua, sua (bool):
  1185. True, if the unit is classified as a noise unit, i.e.,
  1186. not considered neural activity (noise), a multi-unit
  1187. (mua), or a single unit (sua).
  1188. SNR (float):
  1189. Signal to noise ratio of SUA/MUA waveforms. A higher
  1190. value indicates that the unit could be better
  1191. distinguished in the spike detection and spike sorting
  1192. procedure.
  1193. spike_duration (float):
  1194. Approximate duration of the spikes of SUAs/MUAs in
  1195. microseconds.
  1196. spike_amplitude (float):
  1197. Maximum amplitude of the spike waveform.
  1198. spike_count (int):
  1199. Number of spikes sorted into this unit.
  1200. AnalogSignal annotations:
  1201. nsx (int):
  1202. nsX file the signal was loaded from, e.g., 5 indicates
  1203. the .ns5 file.
  1204. channel_id (int):
  1205. Channel ID (Blackrock ID) from which the signal was
  1206. loaded.
  1207. connector_aligned_id (int):
  1208. Connector-aligned channel ID from which the signal was
  1209. loaded. This is a channel ID between 1 and 100 that is
  1210. related to the location of an electrode on the Utah
  1211. array and thus common across different arrays
  1212. (independent of the Blackrock channel ID). The ID
  1213. considers a top-view of the array with the connector
  1214. wires extruding to the right. Electrodes are then
  1215. numbered from bottom left to top right:
  1216. 91 92 ... 99 100 \
  1217. 81 82 ... 89 90 \
  1218. ... ... --- Connector Wires
  1219. 11 12 ... 19 20 /
  1220. 1 2 ... 9 10 /
  1221. electrode_reject_XXX (bool):
  1222. For different filter ranges XXX (as defined in the odML
  1223. file), if this variable is True it indicates whether
  1224. the spikes were recorded on an electrode that should be
  1225. rejected based on preprocessing analysis for removing
  1226. electrodes due to noise/artefacts in the respective
  1227. frequency range.
  1228. filter_shift_correction (Quantity):
  1229. If the parameter correct_filter_shift is True, and a
  1230. shift estimate was found in the odML, this annotation
  1231. indicates the amount of time by which the signal was
  1232. shifted. I.e., adding this number to t_start will
  1233. result in the uncorrected, originally recorded time
  1234. axis.
  1235. Spiketrain annotations:
  1236. unit_id (int):
  1237. ID of the unit from which the spikes were recorded.
  1238. channel_id (int):
  1239. Channel ID (Blackrock ID) from which the spikes were
  1240. loaded.
  1241. connector_aligned_id (int):
  1242. Connector-aligned channel ID from which the spikes were
  1243. loaded. This is a channel ID between 1 and 100 that is
  1244. related to the location of an electrode on the Utah
  1245. array and thus common across different arrays
  1246. (independent of the Blackrock channel ID). The ID
  1247. considers a top-view of the array with the connector
  1248. wires extruding to the right. Electrodes are then
  1249. numbered from bottom left to top right:
  1250. 91 92 ... 99 100 \
  1251. 81 82 ... 89 90 \
  1252. ... ... --- Connector Wires
  1253. 11 12 ... 19 20 /
  1254. 1 2 ... 9 10 /
  1255. electrode_reject_XXX (bool):
  1256. For different filter ranges XXX (as defined in the odML
  1257. file), if this variable is True it indicates whether
  1258. the spikes were recorded on an electrode that should be
  1259. rejected based on preprocessing analysis for removing
  1260. electrodes due to noise/artefacts in the respective
  1261. frequency range.
  1262. noise, mua, sua (bool):
  1263. True, if the unit is classified as a noise unit, i.e.,
  1264. not considered neural activity (noise), a multi-unit
  1265. (mua), or a single unit (sua).
  1266. SNR (float):
  1267. Signal to noise ratio of SUA/MUA waveforms. A higher
  1268. value indicates that the unit could be better
  1269. distinguished in the spike detection and spike sorting
  1270. procedure.
  1271. spike_duration (float):
  1272. Approximate duration of the spikes of SUAs/MUAs in
  1273. microseconds.
  1274. spike_amplitude (float):
  1275. Maximum amplitude of the spike waveform.
  1276. spike_count (int):
  1277. Number of spikes sorted into this unit.
  1278. Event annotations:
  1279. The resulting Block contains three Event objects with the
  1280. following names:
  1281. "DigitalTrialEvents' contains all digitally recorded events
  1282. returned by BlackrockIO, annotated with semantic labels
  1283. in accordance with the reach-to-grasp experiment (e.g.,
  1284. 'TS-ON').
  1285. 'AnalogTrialEvents' contains events extracted from the
  1286. analog behavioral signals during preprocessing and
  1287. stored in the odML (e.g., 'OT').
  1288. 'TrialEvents' contains all events of DigitalTrialEvents and
  1289. AnalogTrialEvents merged into a single Neo object.
  1290. Each annotation is a list containing one entry per time
  1291. point stored in the event.
  1292. trial_event_labels (list of str):
  1293. Name identifying the name of the event, e.g., 'TS-ON'.
  1294. trial_id (list of int):
  1295. Trial ID the event belongs to.
  1296. trial_timestamp_id (list of int):
  1297. Timestamp-based trial ID (equivalent to the time of TS-
  1298. ON of a trial) the event belongs to.
  1299. belongs_to_trialtype (str):
  1300. String identifying the trial type (e.g., SGHF) the
  1301. trial belongs to.
  1302. performance_in_trial (list of int):
  1303. Performance code of the trial that the event belongs
  1304. to. Compare to the performance_codes and
  1305. performance_str attributes of ReachGraspIO class.
  1306. trial_reject_XXX:
  1307. For different filter ranges XXX (defined in the odML
  1308. file), if True this variable indicates whether the
  1309. trial was rejected based on preprocessing analysis.
  1310. """
  1311. if not name:
  1312. name = 'Reachgrasp Recording Data Block'
  1313. if not description:
  1314. description = \
  1315. "Block of reach-to-grasp project data from Blackrock file set."
  1316. # Load neo block
  1317. bl = BlackrockIO.read_block(
  1318. self, index=index, name=name, description=description,
  1319. nsx_to_load=nsx_to_load, n_starts=n_starts, n_stops=n_stops,
  1320. channels=channels, units=units, load_waveforms=load_waveforms,
  1321. load_events=load_events, scaling=scaling, lazy=lazy,
  1322. cascade=cascade)
  1323. bl.annotate(conditions=[])
  1324. for seg in bl.segments:
  1325. if load_events and not lazy:
  1326. if 'condition' in list(seg.annotations):
  1327. bl.annotations['conditions'].append(
  1328. seg.annotations['condition'])
  1329. if self.odmldoc:
  1330. self.__annotate_block_with_odml(bl)
  1331. for chidx in bl.channel_indexes:
  1332. self.__annotate_channelindex_with_odml(chidx)
  1333. self.__annotate_units_with_odml(chidx.units)
  1334. return bl
  1335. def read_segment(
  1336. self, n_start, n_stop, name=None, description=None, index=None,
  1337. nsx_to_load='none', channels=range(1, 97), units='none',
  1338. load_waveforms=False, load_events=False, scaling='raw',
  1339. correct_filter_shifts=True, lazy=False, cascade=True):
  1340. """
  1341. Reads file contents as a Neo Block.
  1342. The Block contains one Segment for each entry in zip(n_starts,
  1343. n_stops). If these parameters are not specified, the default is
  1344. to store all data in one Segment.
  1345. The Block contains one ChannelIndex per channel.
  1346. Args:
  1347. n_start (Quantity):
  1348. Start time of maximum time range of signals contained in this
  1349. segment.
  1350. n_stop (Quantity):
  1351. Stop time of maximum time range of signals contained in this
  1352. segment.
  1353. name (None, string):
  1354. If None, name is set to default, otherwise it is set to user
  1355. input.
  1356. description (None, string):
  1357. If None, description is set to default, otherwise it is set to
  1358. user input.
  1359. index (None, int):
  1360. If not None, index of segment is set to user index.
  1361. nsx_to_load (int, list, str):
  1362. ID(s) of nsx file(s) from which to load data, e.g., if set to
  1363. 5 only data from the ns5 file are loaded. If 'none' or empty
  1364. list, no nsx files and therefore no analog signals are loaded.
  1365. If 'all', data from all available nsx are loaded.
  1366. channels (int, list, str):
  1367. Channel id(s) from which to load data. If 'none' or empty list,
  1368. no channels and therefore no analog signal or spiketrains are
  1369. loaded. If 'all', all available channels are loaded. By
  1370. default, all neural channels (1-96) are loaded.
  1371. units (int, list, str, dict):
  1372. ID(s) of unit(s) to load. If 'none' or empty list, no units and
  1373. therefore no spiketrains are loaded. If 'all', all available
  1374. units are loaded. If dict, the above can be specified
  1375. individually for each channel (keys), e.g. {1: 5, 2: 'all'}
  1376. loads unit 5 from channel 1 and all units from channel 2.
  1377. load_waveforms (boolean):
  1378. If True, waveforms are attached to all loaded spiketrains.
  1379. load_events (boolean):
  1380. If True, all recorded events are loaded.
  1381. scaling (str):
  1382. Determines whether time series of individual
  1383. electrodes/channels are returned as AnalogSignals containing
  1384. raw integer samples ('raw'), or scaled to arrays of floats
  1385. representing voltage ('voltage'). Note that for file
  1386. specification 2.1 and lower, the option 'voltage' requires a
  1387. nev file to be present.
  1388. correct_filter_shifts (bool):
  1389. If True, shifts of the online-filtered neural signals (e.g.,
  1390. ns2, channels 1-128) are corrected by time-shifting the signal
  1391. by a heuristically determined estimate stored in the metadata,
  1392. in the property EstimatedShift, under the path
  1393. /Cerebus/NeuralSignalProcessor/NeuralSignals/Filter_nsX/
  1394. lazy (boolean):
  1395. If True, only the shape of the data is loaded.
  1396. cascade (boolean):
  1397. If True, only the segment without children is returned.
  1398. Returns:
  1399. Segment (neo.segment.Segment):
  1400. Segment linking to all loaded Neo objects. See documentation of
  1401. read_block() for a full list of annotations per Neo object.
  1402. """
  1403. # Load neo block
  1404. seg = BlackrockIO.read_segment(
  1405. self, n_start, n_stop, name=name, description=description,
  1406. index=index, nsx_to_load=nsx_to_load, channels=channels,
  1407. units=units, load_waveforms=load_waveforms,
  1408. load_events=load_events, scaling=scaling, lazy=lazy,
  1409. cascade=cascade)
  1410. for asig in seg.analogsignals:
  1411. self.__annotate_analogsignals_with_odml(asig)
  1412. if correct_filter_shifts:
  1413. self.__correct_filter_shifts(asig)
  1414. if load_events and not lazy:
  1415. for ev in seg.events:
  1416. # Modify digital trial events to include semantic event
  1417. # informations
  1418. if ev.name == 'digital_input_port':
  1419. self.__annotate_dig_trial_events(ev)
  1420. self.__add_rejection_to_event(ev)
  1421. cnd = self.__extract_task_condition(
  1422. ev.annotations['belongs_to_trialtype'])
  1423. seg.annotate(condition=cnd)
  1424. # If digital trial events exist, extract analog events from odML
  1425. # and create one common event array
  1426. if len(seg.events) > 0 and self.odmldoc:
  1427. analog_event = self.__extract_analog_events_from_odml(
  1428. seg.t_start, seg.t_stop)
  1429. self.__add_rejection_to_event(analog_event)
  1430. seg.events.append(analog_event)
  1431. merged_event = self.__merge_digital_analog_events(
  1432. seg.events)
  1433. self.__add_rejection_to_event(merged_event)
  1434. seg.events.append(merged_event)
  1435. return seg
  1436. if __name__ == '__main__':
  1437. pass