reachgraspio.py 85 KB

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