brainwaresrcio.py 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530
  1. """
  2. Class for reading from Brainware SRC files
  3. SRC files are binary files for holding spike data. They are broken up into
  4. nested data sequences of different types, with each type of sequence identified
  5. by a unique ID number. This allows new versions of sequences to be included
  6. without breaking backwards compatibility, since new versions can just be given
  7. a new ID number.
  8. The ID numbers and the format of the data they contain were taken from the
  9. Matlab-based reader function supplied with BrainWare. The python code,
  10. however, was implemented from scratch in Python using Python idioms.
  11. There are some situations where BrainWare data can overflow the SRC file,
  12. resulting in a corrupt file. Neither BrainWare nor the Matlab-based
  13. reader can read such files. This software, however, will try to recover
  14. the data, and in most cases can do so successfully.
  15. Each SRC file can hold the equivalent of multiple Neo Blocks.
  16. Brainware was developed by Dr. Jan Schnupp and is availabe from
  17. Tucker Davis Technologies, Inc.
  18. http://www.tdt.com/downloads.htm
  19. Neither Dr. Jan Schnupp nor Tucker Davis Technologies, Inc. had any part in the
  20. development of this code
  21. The code is implemented with the permission of Dr. Jan Schnupp
  22. Note when porting ChannelIndex/Unit to Group (Samuel Garcia).
  23. The ChannelIndex was used as group of units.
  24. To avoid now a "group of group" each units is directly a "Group"'.
  25. Author: Todd Jennings
  26. """
  27. # import needed core python modules
  28. from datetime import datetime, timedelta
  29. from itertools import chain
  30. import logging
  31. import os.path
  32. # numpy and quantities are already required by neo
  33. import numpy as np
  34. import quantities as pq
  35. # needed core neo modules
  36. from neo.core import (Block, Event,
  37. Group, Segment, SpikeTrain, Unit)
  38. # need to subclass BaseIO
  39. from neo.io.baseio import BaseIO
  40. LOGHANDLER = logging.StreamHandler()
  41. class BrainwareSrcIO(BaseIO):
  42. """
  43. Class for reading Brainware Spike ReCord files with the extension '.src'
  44. The read_block method returns the first Block of the file. It will
  45. automatically close the file after reading.
  46. The read method is the same as read_block.
  47. The read_all_blocks method automatically reads all Blocks. It will
  48. automatically close the file after reading.
  49. The read_next_block method will return one Block each time it is called.
  50. It will automatically close the file and reset to the first Block
  51. after reading the last block.
  52. Call the close method to close the file and reset this method
  53. back to the first Block.
  54. The _isopen property tells whether the file is currently open and
  55. reading or closed.
  56. Note 1:
  57. The first Unit in each Group is always
  58. UnassignedSpikes, which has a SpikeTrain for each Segment containing
  59. all the spikes not assigned to any Unit in that Segment.
  60. Note 2:
  61. The first Segment in each Block is always Comments, which stores all
  62. comments as an Event object.
  63. Note 3:
  64. The parameters from the BrainWare table for each condition are stored
  65. in the Segment annotations. If there are multiple repetitions of
  66. a condition, each repetition is stored as a separate Segment.
  67. Note 4:
  68. There is always only one Group.
  69. Usage:
  70. >>> from neo.io.brainwaresrcio import BrainwareSrcIO
  71. >>> srcfile = BrainwareSrcIO(filename='multi_500ms_mulitrep_ch1.src')
  72. >>> blk1 = srcfile.read()
  73. >>> blk2 = srcfile.read_block()
  74. >>> blks = srcfile.read_all_blocks()
  75. >>> print blk1.segments
  76. >>> print blk1.segments[0].spiketrains
  77. >>> print blk1.groups
  78. >>> print blk1.groups[0].name
  79. >>> print blk2
  80. >>> print blk2[0].segments
  81. >>> print blks
  82. >>> print blks[0].segments
  83. """
  84. is_readable = True # This class can only read data
  85. is_writable = False # write is not supported
  86. # This class is able to directly or indirectly handle the following objects
  87. supported_objects = [Block, Group,
  88. Segment, SpikeTrain, Event]
  89. readable_objects = [Block]
  90. writeable_objects = []
  91. has_header = False
  92. is_streameable = False
  93. # This is for GUI stuff: a definition for parameters when reading.
  94. # This dict should be keyed by object (`Block`). Each entry is a list
  95. # of tuple. The first entry in each tuple is the parameter name. The
  96. # second entry is a dict with keys 'value' (for default value),
  97. # and 'label' (for a descriptive name).
  98. # Note that if the highest-level object requires parameters,
  99. # common_io_test will be skipped.
  100. read_params = {Block: []}
  101. # does not support write so no GUI stuff
  102. write_params = None
  103. name = 'Brainware SRC File'
  104. extensions = ['src']
  105. mode = 'file'
  106. def __init__(self, filename=None):
  107. """
  108. Arguments:
  109. filename: the filename
  110. """
  111. BaseIO.__init__(self)
  112. # log the __init__
  113. self.logger.info('__init__')
  114. # this stores the filename of the current object, exactly as it is
  115. # provided when the instance is initialized.
  116. self._filename = filename
  117. # this store the filename without the path
  118. self._file_origin = filename
  119. # This stores the file object for the current file
  120. self._fsrc = None
  121. # This stores the current Block
  122. self._blk = None
  123. # This stores the current Segment for easy access
  124. # It is equivalent to self._blk.segments[-1]
  125. self._seg0 = None
  126. # this stores a dictionary of the Block's Group (Units) by name,
  127. # making it easier and faster to retrieve Units by name later
  128. # UnassignedSpikes and Units accessed by index are not stored here
  129. self._unitdict = {}
  130. # this stores the current Unit
  131. self._unit0 = None
  132. # if the file has a list with negative length, the rest of the file's
  133. # list lengths are unreliable, so we need to store this value for the
  134. # whole file
  135. self._damaged = False
  136. # this stores an empty SpikeTrain which is used in various places.
  137. self._default_spiketrain = None
  138. @property
  139. def _isopen(self):
  140. """
  141. This property tells whether the SRC file associated with the IO object
  142. is open.
  143. """
  144. return self._fsrc is not None
  145. def _opensrc(self):
  146. """
  147. Open the file if it isn't already open.
  148. """
  149. # if the file isn't already open, open it and clear the Blocks
  150. if not self._fsrc or self._fsrc.closed:
  151. self._fsrc = open(self._filename, 'rb')
  152. # figure out the filename of the current file
  153. self._file_origin = os.path.basename(self._filename)
  154. def close(self):
  155. """
  156. Close the currently-open file and reset the current reading point.
  157. """
  158. self.logger.info('close')
  159. if self._isopen and not self._fsrc.closed:
  160. self._fsrc.close()
  161. # we also need to reset all per-file attributes
  162. self._damaged = False
  163. self._fsrc = None
  164. self._seg0 = None
  165. self._file_origin = None
  166. self._lazy = False
  167. self._default_spiketrain = None
  168. def read_block(self, lazy=False, **kargs):
  169. """
  170. Reads the first Block from the Spike ReCording file "filename"
  171. generated with BrainWare.
  172. If you wish to read more than one Block, please use read_all_blocks.
  173. """
  174. assert not lazy, 'Do not support lazy'
  175. # there are no keyargs implemented to so far. If someone tries to pass
  176. # them they are expecting them to do something or making a mistake,
  177. # neither of which should pass silently
  178. if kargs:
  179. raise NotImplementedError('This method does not have any '
  180. 'arguments implemented yet')
  181. blockobj = self.read_next_block()
  182. self.close()
  183. return blockobj
  184. def read_next_block(self, **kargs):
  185. """
  186. Reads a single Block from the Spike ReCording file "filename"
  187. generated with BrainWare.
  188. Each call of read will return the next Block until all Blocks are
  189. loaded. After the last Block, the file will be automatically closed
  190. and the progress reset. Call the close method manually to reset
  191. back to the first Block.
  192. """
  193. # there are no keyargs implemented to so far. If someone tries to pass
  194. # them they are expecting them to do something or making a mistake,
  195. # neither of which should pass silently
  196. if kargs:
  197. raise NotImplementedError('This method does not have any '
  198. 'arguments implemented yet')
  199. self._opensrc()
  200. # create _default_spiketrain here for performance reasons
  201. self._default_spiketrain = self._init_default_spiketrain.copy()
  202. self._default_spiketrain.file_origin = self._file_origin
  203. # create the Block and the contents all Blocks of from IO share
  204. self._blk = Block(file_origin=self._file_origin)
  205. self._seg0 = Segment(name='Comments', file_origin=self._file_origin)
  206. self._unit0 = Group(name='UnassignedSpikes',
  207. elliptic=[], boundaries=[],
  208. timestamp=[], max_valid=[])
  209. self._blk.groups.append(self._unit0)
  210. self._blk.segments.append(self._seg0)
  211. # this actually reads the contents of the Block
  212. result = []
  213. while hasattr(result, '__iter__'):
  214. try:
  215. result = self._read_by_id()
  216. except:
  217. self.close()
  218. raise
  219. # since we read at a Block level we always do this
  220. self._blk.create_many_to_one_relationship()
  221. # put the Block in a local object so it can be gargabe collected
  222. blockobj = self._blk
  223. # reset the per-Block attributes
  224. self._blk = None
  225. self._unitdict = {}
  226. # combine the comments into one big event
  227. self._combine_segment_events(self._seg0)
  228. # result is None iff the end of the file is reached, so we can
  229. # close the file
  230. # this notification is not helpful if using the read method with
  231. # cascading, since the user will know it is done when the method
  232. # returns a value
  233. if result is None:
  234. self.logger.info('Last Block read. Closing file.')
  235. self.close()
  236. return blockobj
  237. def read_all_blocks(self, lazy=False, **kargs):
  238. """
  239. Reads all Blocks from the Spike ReCording file "filename"
  240. generated with BrainWare.
  241. The progress in the file is reset and the file closed then opened again
  242. prior to reading.
  243. The file is automatically closed after reading completes.
  244. """
  245. # there are no keyargs implemented to so far. If someone tries to pass
  246. # them they are expecting them to do something or making a mistake,
  247. # neither of which should pass silently
  248. assert not lazy, 'Do not support lazy'
  249. if kargs:
  250. raise NotImplementedError('This method does not have any '
  251. 'argument implemented yet')
  252. self.close()
  253. self._opensrc()
  254. # Read each Block.
  255. # After the last Block self._isopen is set to False, so this make a
  256. # good way to determine when to stop
  257. blocks = []
  258. while self._isopen:
  259. try:
  260. blocks.append(self.read_next_block())
  261. except:
  262. self.close()
  263. raise
  264. return blocks
  265. def _convert_timestamp(self, timestamp, start_date=datetime(1899, 12, 30)):
  266. """
  267. _convert_timestamp(timestamp, start_date) - convert a timestamp in
  268. brainware src file units to a python datetime object.
  269. start_date defaults to 1899.12.30 (ISO format), which is the start date
  270. used by all BrainWare SRC data Blocks so far. If manually specified
  271. it should be a datetime object or any other object that can be added
  272. to a timedelta object.
  273. """
  274. # datetime + timedelta = datetime again.
  275. try:
  276. timestamp = convert_brainwaresrc_timestamp(timestamp, start_date)
  277. except OverflowError as err:
  278. timestamp = start_date
  279. self.logger.exception('_convert_timestamp overflow')
  280. return timestamp
  281. # -------------------------------------------------------------------------
  282. # -------------------------------------------------------------------------
  283. # All methods from here on are private. They are not intended to be used
  284. # on their own, although methods that could theoretically be called on
  285. # their own are marked as such. All private methods could be renamed,
  286. # combined, or split at any time. All private methods prefixed by
  287. # "__read" or "__skip" will alter the current place in the file.
  288. # -------------------------------------------------------------------------
  289. # -------------------------------------------------------------------------
  290. def _read_by_id(self):
  291. """
  292. Reader for generic data
  293. BrainWare SRC files are broken up into data sequences that are
  294. identified by an ID code. This method determines the ID code and calls
  295. the method to read the data sequence with that ID code. See the
  296. _ID_DICT attribute for a dictionary of code/method pairs.
  297. IMPORTANT!!!
  298. This is the only private method that can be called directly.
  299. The rest of the private methods can only safely be called by this
  300. method or by other private methods, since they depend on the
  301. current position in the file.
  302. """
  303. try:
  304. # uint16 -- the ID code of the next sequence
  305. seqid = np.fromfile(self._fsrc, dtype=np.uint16, count=1).item()
  306. except ValueError:
  307. # return a None if at EOF. Other methods use None to recognize
  308. # an EOF
  309. return None
  310. # using the seqid, get the reader function from the reader dict
  311. readfunc = self._ID_DICT.get(seqid)
  312. if readfunc is None:
  313. if seqid <= 0:
  314. # return if end-of-sequence ID code. This has to be 0.
  315. # just calling "return" will return a None which is used as an
  316. # EOF indicator
  317. return 0
  318. else:
  319. # return a warning if the key is invalid
  320. # (this is consistent with the official behavior,
  321. # even the official reference files have invalid keys
  322. # when using the official reference reader matlab
  323. # scripts
  324. self.logger.warning('unknown ID: %s', seqid)
  325. return []
  326. try:
  327. # run the function to get the data
  328. return readfunc(self)
  329. except (EOFError, UnicodeDecodeError) as err:
  330. # return a warning if the EOF is reached in the middle of a method
  331. self.logger.exception('Premature end of file')
  332. return None
  333. # -------------------------------------------------------------------------
  334. # -------------------------------------------------------------------------
  335. # These are helper methods. They don't read from the file, so it
  336. # won't harm the reading process to call them, but they are only relevant
  337. # when used in other private methods.
  338. #
  339. # These are tuned to the particular needs of this IO class, they are
  340. # unlikely to work properly if used with another file format.
  341. # -------------------------------------------------------------------------
  342. # -------------------------------------------------------------------------
  343. def _assign_sequence(self, data_obj):
  344. """
  345. _assign_sequence(data_obj) - Try to guess where an unknown sequence
  346. should go based on its class. Warning are issued if this method is
  347. used since manual reorganization may be needed.
  348. """
  349. if isinstance(data_obj, Group):
  350. self.logger.warning('Unknown Group found, adding to Group list')
  351. self._blk.groups.append(data_obj)
  352. if data_obj.name:
  353. self._unitdict[data_obj.name] = data_obj
  354. elif isinstance(data_obj, Segment):
  355. self.logger.warning('Unknown Segment found, '
  356. 'adding to Segments list')
  357. self._blk.segments.append(data_obj)
  358. elif isinstance(data_obj, Event):
  359. self.logger.warning('Unknown Event found, '
  360. 'adding to comment Events list')
  361. self._seg0.events.append(data_obj)
  362. elif isinstance(data_obj, SpikeTrain):
  363. self.logger.warning('Unknown SpikeTrain found, '
  364. 'adding to the UnassignedSpikes Unit')
  365. self._unit0.spiketrains.append(data_obj)
  366. elif hasattr(data_obj, '__iter__') and not isinstance(data_obj, str):
  367. for sub_obj in data_obj:
  368. self._assign_sequence(sub_obj)
  369. else:
  370. if self.logger.isEnabledFor(logging.WARNING):
  371. self.logger.warning('Unrecognized sequence of type %s found, '
  372. 'skipping', type(data_obj))
  373. _default_datetime = datetime(1, 1, 1)
  374. _default_t_start = pq.Quantity(0., units=pq.ms, dtype=np.float32)
  375. _init_default_spiketrain = SpikeTrain(times=pq.Quantity([], units=pq.ms,
  376. dtype=np.float32),
  377. t_start=pq.Quantity(0, units=pq.ms,
  378. dtype=np.float32
  379. ),
  380. t_stop=pq.Quantity(1, units=pq.ms,
  381. dtype=np.float32),
  382. waveforms=pq.Quantity([[[]]],
  383. dtype=np.int8,
  384. units=pq.mV),
  385. dtype=np.float32, copy=False,
  386. timestamp=_default_datetime,
  387. respwin=np.array([], dtype=np.int32),
  388. dama_index=-1,
  389. trig2=pq.Quantity([], units=pq.ms,
  390. dtype=np.uint8),
  391. side='')
  392. def _combine_events(self, events):
  393. """
  394. _combine_events(events) - combine a list of Events
  395. with single events into one long Event
  396. """
  397. if not events:
  398. event = Event(times=pq.Quantity([], units=pq.s),
  399. labels=np.array([], dtype='U'),
  400. senders=np.array([], dtype='S'),
  401. t_start=0)
  402. return event
  403. times = []
  404. labels = []
  405. senders = []
  406. for event in events:
  407. times.append(event.times.magnitude)
  408. if event.labels.shape == (1,):
  409. labels.append(event.labels[0])
  410. else:
  411. raise AssertionError("This single event has multiple labels in an array with "
  412. "shape {} instead of a single label.".
  413. format(event.labels.shape))
  414. senders.append(event.annotations['sender'])
  415. times = np.array(times, dtype=np.float32)
  416. t_start = times.min()
  417. times = pq.Quantity(times - t_start, units=pq.d).rescale(pq.s)
  418. labels = np.array(labels, dtype='U')
  419. senders = np.array(senders)
  420. event = Event(times=times, labels=labels,
  421. t_start=t_start.tolist(), senders=senders)
  422. return event
  423. def _combine_segment_events(self, segment):
  424. """
  425. _combine_segment_events(segment)
  426. Combine all Events in a segment.
  427. """
  428. event = self._combine_events(segment.events)
  429. event_t_start = event.annotations.pop('t_start')
  430. segment.rec_datetime = self._convert_timestamp(event_t_start)
  431. segment.events = [event]
  432. event.segment = segment
  433. def _combine_spiketrains(self, spiketrains):
  434. """
  435. _combine_spiketrains(spiketrains) - combine a list of SpikeTrains
  436. with single spikes into one long SpikeTrain
  437. """
  438. if not spiketrains:
  439. return self._default_spiketrain.copy()
  440. if hasattr(spiketrains[0], 'waveforms') and len(spiketrains) == 1:
  441. train = spiketrains[0]
  442. return train
  443. if hasattr(spiketrains[0], 't_stop'):
  444. # workaround for bug in some broken files
  445. istrain = [hasattr(utrain, 'waveforms') for utrain in spiketrains]
  446. if not all(istrain):
  447. goodtrains = [itrain for i, itrain in enumerate(spiketrains)
  448. if istrain[i]]
  449. badtrains = [itrain for i, itrain in enumerate(spiketrains)
  450. if not istrain[i]]
  451. spiketrains = (goodtrains +
  452. [self._combine_spiketrains(badtrains)])
  453. spiketrains = [itrain for itrain in spiketrains if itrain.size > 0]
  454. if not spiketrains:
  455. return self._default_spiketrain.copy()
  456. # get the times of the spiketrains and combine them
  457. waveforms = [itrain.waveforms for itrain in spiketrains]
  458. rawtrains = np.array(np.concatenate(spiketrains, axis=1))
  459. times = pq.Quantity(rawtrains, units=pq.ms, copy=False)
  460. lens1 = np.array([wave.shape[1] for wave in waveforms])
  461. lens2 = np.array([wave.shape[2] for wave in waveforms])
  462. if lens1.max() != lens1.min() or lens2.max() != lens2.min():
  463. lens1 = lens1.max() - lens1
  464. lens2 = lens2.max() - lens2
  465. waveforms = [np.pad(waveform,
  466. ((0, 0), (0, len1), (0, len2)),
  467. 'constant')
  468. for waveform, len1, len2 in zip(waveforms,
  469. lens1,
  470. lens2)]
  471. waveforms = np.concatenate(waveforms, axis=0)
  472. # extract the trig2 annotation
  473. trig2 = np.array(np.concatenate([itrain.annotations['trig2'] for
  474. itrain in spiketrains], axis=1))
  475. trig2 = pq.Quantity(trig2, units=pq.ms)
  476. elif hasattr(spiketrains[0], 'units'):
  477. return self._combine_spiketrains([spiketrains])
  478. else:
  479. times, waveforms, trig2 = zip(*spiketrains)
  480. times = np.concatenate(times, axis=0)
  481. # get the times of the SpikeTrains and combine them
  482. times = pq.Quantity(times, units=pq.ms, copy=False)
  483. # get the waveforms of the SpikeTrains and combine them
  484. # these should be a 3D array with the first axis being the spike,
  485. # the second axis being the recording channel (there is only one),
  486. # and the third axis being the actual waveform
  487. waveforms = np.concatenate(waveforms, axis=0)
  488. # extract the trig2 annotation
  489. trig2 = pq.Quantity(np.hstack(trig2),
  490. units=pq.ms, copy=False)
  491. if not times.size:
  492. return self._default_spiketrain.copy()
  493. # get the maximum time
  494. t_stop = times[-1] * 2.
  495. waveforms = pq.Quantity(waveforms, units=pq.mV, copy=False)
  496. train = SpikeTrain(times=times, copy=False,
  497. t_start=self._default_t_start.copy(), t_stop=t_stop,
  498. file_origin=self._file_origin,
  499. waveforms=waveforms,
  500. timestamp=self._default_datetime,
  501. respwin=np.array([], dtype=np.int32),
  502. dama_index=-1, trig2=trig2, side='')
  503. return train
  504. # -------------------------------------------------------------------------
  505. # -------------------------------------------------------------------------
  506. # IMPORTANT!!!
  507. # These are private methods implementing the internal reading mechanism.
  508. # Due to the way BrainWare SRC files are structured, they CANNOT be used
  509. # on their own. Calling these manually will almost certainly alter your
  510. # position in the file in an unrecoverable manner, whether they throw
  511. # an exception or not.
  512. # -------------------------------------------------------------------------
  513. # -------------------------------------------------------------------------
  514. def __read_str(self, numchars=1, utf=None):
  515. """
  516. Read a string of a specific length.
  517. """
  518. rawstr = np.fromfile(self._fsrc, dtype='S%s' % numchars, count=1).item()
  519. return rawstr.decode('utf-8')
  520. def __read_annotations(self):
  521. """
  522. Read the stimulus grid properties.
  523. -------------------------------------------------------------------
  524. Returns a dictionary containing the parameter names as keys and the
  525. parameter values as values.
  526. The returned object must be added to the Block.
  527. ID: 29109
  528. """
  529. # int16 -- number of stimulus parameters
  530. numelements = np.fromfile(self._fsrc, dtype=np.int16, count=1)[0]
  531. if not numelements:
  532. return {}
  533. # [data sequence] * numelements -- parameter names
  534. names = []
  535. for i in range(numelements):
  536. # {skip} = byte (char) -- skip one byte
  537. self._fsrc.seek(1, 1)
  538. # uint8 -- length of next string
  539. numchars = np.fromfile(self._fsrc, dtype=np.uint8, count=1).item()
  540. # if there is no name, make one up
  541. if not numchars:
  542. name = 'param%s' % i
  543. else:
  544. # char * numchars -- parameter name string
  545. name = self.__read_str(numchars)
  546. # if the name is already in there, add a unique number to it
  547. # so it isn't overwritten
  548. if name in names:
  549. name = name + str(i)
  550. names.append(name)
  551. # float32 * numelements -- an array of parameter values
  552. values = np.fromfile(self._fsrc, dtype=np.float32,
  553. count=numelements)
  554. # combine the names and values into a dict
  555. # the dict will be added to the annotations
  556. annotations = dict(zip(names, values))
  557. return annotations
  558. def __read_annotations_old(self):
  559. """
  560. Read the stimulus grid properties.
  561. Returns a dictionary containing the parameter names as keys and the
  562. parameter values as values.
  563. ------------------------------------------------
  564. The returned objects must be added to the Block.
  565. This reads an old version of the format that does not store paramater
  566. names, so placeholder names are created instead.
  567. ID: 29099
  568. """
  569. # int16 * 14 -- an array of parameter values
  570. values = np.fromfile(self._fsrc, dtype=np.int16, count=14)
  571. # create dummy names and combine them with the values in a dict
  572. # the dict will be added to the annotations
  573. params = ['param%s' % i for i in range(len(values))]
  574. annotations = dict(zip(params, values))
  575. return annotations
  576. def __read_comment(self):
  577. """
  578. Read a single comment.
  579. The comment is stored as an Event in Segment 0, which is
  580. specifically for comments.
  581. ----------------------
  582. Returns an empty list.
  583. The returned object is already added to the Block.
  584. No ID number: always called from another method
  585. """
  586. # float64 -- timestamp (number of days since dec 30th 1899)
  587. time = np.fromfile(self._fsrc, dtype=np.double, count=1)[0]
  588. # int16 -- length of next string
  589. numchars1 = np.fromfile(self._fsrc, dtype=np.int16, count=1).item()
  590. # char * numchars -- the one who sent the comment
  591. sender = self.__read_str(numchars1)
  592. # int16 -- length of next string
  593. numchars2 = np.fromfile(self._fsrc, dtype=np.int16, count=1).item()
  594. # char * numchars -- comment text
  595. text = self.__read_str(numchars2, utf=False)
  596. comment = Event(times=pq.Quantity(time, units=pq.d), labels=[text],
  597. sender=sender, file_origin=self._file_origin)
  598. self._seg0.events.append(comment)
  599. return []
  600. def __read_list(self):
  601. """
  602. Read a list of arbitrary data sequences
  603. It only says how many data sequences should be read. These sequences
  604. are then read by their ID number.
  605. Note that lists can be nested.
  606. If there are too many sequences (for instance if there are a large
  607. number of spikes in a Segment) then a negative number will be returned
  608. for the number of data sequences to read. In this case the method
  609. tries to guess. This also means that all future list data sequences
  610. have unreliable lengths as well.
  611. -------------------------------------------
  612. Returns a list of objects.
  613. Whether these objects need to be added to the Block depends on the
  614. object in question.
  615. There are several data sequences that have identical formats but are
  616. used in different situations. That means this data sequences has
  617. multiple ID numbers.
  618. ID: 29082
  619. ID: 29083
  620. ID: 29091
  621. ID: 29093
  622. """
  623. # int16 -- number of sequences to read
  624. numelements = np.fromfile(self._fsrc, dtype=np.int16, count=1)[0]
  625. # {skip} = bytes * 4 (int16 * 2) -- skip four bytes
  626. self._fsrc.seek(4, 1)
  627. if numelements == 0:
  628. return []
  629. if not self._damaged and numelements < 0:
  630. self._damaged = True
  631. self.logger.error('Negative sequence count %s, file damaged',
  632. numelements)
  633. if not self._damaged:
  634. # read the sequences into a list
  635. seq_list = [self._read_by_id() for _ in range(numelements)]
  636. else:
  637. # read until we get some indication we should stop
  638. seq_list = []
  639. # uint16 -- the ID of the next sequence
  640. seqidinit = np.fromfile(self._fsrc, dtype=np.uint16, count=1)[0]
  641. # {rewind} = byte * 2 (int16) -- move back 2 bytes, i.e. go back to
  642. # before the beginning of the seqid
  643. self._fsrc.seek(-2, 1)
  644. while 1:
  645. # uint16 -- the ID of the next sequence
  646. seqid = np.fromfile(self._fsrc, dtype=np.uint16, count=1)[0]
  647. # {rewind} = byte * 2 (int16) -- move back 2 bytes, i.e. go
  648. # back to before the beginning of the seqid
  649. self._fsrc.seek(-2, 1)
  650. # if we come across a new sequence, we are at the end of the
  651. # list so we should stop
  652. if seqidinit != seqid:
  653. break
  654. # otherwise read the next sequence
  655. seq_list.append(self._read_by_id())
  656. return seq_list
  657. def __read_segment(self):
  658. """
  659. Read an individual Segment.
  660. A Segment contains a dictionary of parameters, the length of the
  661. recording, a list of Units with their Spikes, and a list of Spikes
  662. not assigned to any Unit. The unassigned spikes are always stored in
  663. Unit 0, which is exclusively for storing these spikes.
  664. -------------------------------------------------
  665. Returns the Segment object created by the method.
  666. The returned object is already added to the Block.
  667. ID: 29106
  668. """
  669. # (data_obj) -- the stimulus parameters for this segment
  670. annotations = self._read_by_id()
  671. annotations['feature_type'] = -1
  672. annotations['go_by_closest_unit_center'] = False
  673. annotations['include_unit_bounds'] = False
  674. # (data_obj) -- SpikeTrain list of unassigned spikes
  675. # these go in the first Unit since it is for unassigned spikes
  676. unassigned_spikes = self._read_by_id()
  677. self._unit0.spiketrains.extend(unassigned_spikes)
  678. # read a list of units and grab the second return value, which is the
  679. # SpikeTrains from this Segment (if we use the Unit we will get all the
  680. # SpikeTrains from that Unit, resuling in duplicates if we are past
  681. # the first Segment
  682. trains = self._read_by_id()
  683. if not trains:
  684. if unassigned_spikes:
  685. # if there are no assigned spikes,
  686. # just use the unassigned spikes
  687. trains = zip(unassigned_spikes)
  688. else:
  689. # if there are no spiketrains at all,
  690. # create an empty spike train
  691. trains = [[self._default_spiketrain.copy()]]
  692. elif hasattr(trains[0], 'dtype'):
  693. # workaround for some broken files
  694. trains = [unassigned_spikes +
  695. [self._combine_spiketrains([trains])]]
  696. else:
  697. # get the second element from each returned value,
  698. # which is the actual SpikeTrains
  699. trains = [unassigned_spikes] + [train[1] for train in trains]
  700. # re-organize by sweeps
  701. trains = zip(*trains)
  702. # int32 -- SpikeTrain length in ms
  703. spiketrainlen = pq.Quantity(np.fromfile(self._fsrc, dtype=np.int32,
  704. count=1)[0], units=pq.ms, copy=False)
  705. segments = []
  706. for train in trains:
  707. # create the Segment and add everything to it
  708. segment = Segment(file_origin=self._file_origin,
  709. **annotations)
  710. segment.spiketrains = train
  711. self._blk.segments.append(segment)
  712. segments.append(segment)
  713. for itrain in train:
  714. # use the SpikeTrain length to figure out the stop time
  715. # t_start is always 0 so we can ignore it
  716. itrain.t_stop = spiketrainlen
  717. return segments
  718. def __read_segment_list(self):
  719. """
  720. Read a list of Segments with comments.
  721. Since comments can occur at any point, whether a recording is happening
  722. or not, it is impossible to reliably assign them to a specific Segment.
  723. For this reason they are always assigned to Segment 0, which is
  724. exclusively used to store comments.
  725. --------------------------------------------------------
  726. Returns a list of the Segments created with this method.
  727. The returned objects are already added to the Block.
  728. ID: 29112
  729. """
  730. # uint8 -- number of electrode channels in the Segment
  731. numchannels = np.fromfile(self._fsrc, dtype=np.uint8, count=1)[0]
  732. # [list of sequences] -- individual Segments
  733. segments = self.__read_list()
  734. while not hasattr(segments[0], 'spiketrains'):
  735. segments = list(chain(*segments))
  736. # char -- "side of brain" info
  737. side = self.__read_str(1)
  738. # int16 -- number of comments
  739. numelements = np.fromfile(self._fsrc, dtype=np.int16, count=1)[0]
  740. # comment_obj * numelements -- comments about the Segments
  741. # we don't know which Segment specifically, though
  742. for _ in range(numelements):
  743. self.__read_comment()
  744. # store what side of the head we are dealing with
  745. for segment in segments:
  746. for spiketrain in segment.spiketrains:
  747. spiketrain.annotations['side'] = side
  748. return segments
  749. def __read_segment_list_v8(self):
  750. """
  751. Read a list of Segments with comments.
  752. This is version 8 of the data sequence.
  753. This is the same as __read_segment_list_var, but can also contain
  754. one or more arbitrary sequences. The class makes an attempt to assign
  755. the sequences when possible, and warns the user when this happens
  756. (see the _assign_sequence method)
  757. --------------------------------------------------------
  758. Returns a list of the Segments created with this method.
  759. The returned objects are already added to the Block.
  760. ID: 29117
  761. """
  762. # segment_collection_var -- this is based off a segment_collection_var
  763. segments = self.__read_segment_list_var()
  764. # uint16 -- the ID of the next sequence
  765. seqid = np.fromfile(self._fsrc, dtype=np.uint16, count=1)[0]
  766. # {rewind} = byte * 2 (int16) -- move back 2 bytes, i.e. go back to
  767. # before the beginning of the seqid
  768. self._fsrc.seek(-2, 1)
  769. if seqid in self._ID_DICT:
  770. # if it is a valid seqid, read it and try to figure out where
  771. # to put it
  772. self._assign_sequence(self._read_by_id())
  773. else:
  774. # otherwise it is a Unit list
  775. self.__read_unit_list()
  776. # {skip} = byte * 2 (int16) -- skip 2 bytes
  777. self._fsrc.seek(2, 1)
  778. return segments
  779. def __read_segment_list_v9(self):
  780. """
  781. Read a list of Segments with comments.
  782. This is version 9 of the data sequence.
  783. This is the same as __read_segment_list_v8, but contains some
  784. additional annotations. These annotations are added to the Segment.
  785. --------------------------------------------------------
  786. Returns a list of the Segments created with this method.
  787. The returned objects are already added to the Block.
  788. ID: 29120
  789. """
  790. # segment_collection_v8 -- this is based off a segment_collection_v8
  791. segments = self.__read_segment_list_v8()
  792. # uint8
  793. feature_type = np.fromfile(self._fsrc, dtype=np.uint8,
  794. count=1)[0]
  795. # uint8
  796. go_by_closest_unit_center = np.fromfile(self._fsrc, dtype=np.bool8,
  797. count=1)[0]
  798. # uint8
  799. include_unit_bounds = np.fromfile(self._fsrc, dtype=np.bool8,
  800. count=1)[0]
  801. # create a dictionary of the annotations
  802. annotations = {'feature_type': feature_type,
  803. 'go_by_closest_unit_center': go_by_closest_unit_center,
  804. 'include_unit_bounds': include_unit_bounds}
  805. # add the annotations to each Segment
  806. for segment in segments:
  807. segment.annotations.update(annotations)
  808. return segments
  809. def __read_segment_list_var(self):
  810. """
  811. Read a list of Segments with comments.
  812. This is the same as __read_segment_list, but contains information
  813. regarding the sampling period. This information is added to the
  814. SpikeTrains in the Segments.
  815. --------------------------------------------------------
  816. Returns a list of the Segments created with this method.
  817. The returned objects are already added to the Block.
  818. ID: 29114
  819. """
  820. # float32 -- DA conversion clock period in microsec
  821. sampling_period = pq.Quantity(np.fromfile(self._fsrc,
  822. dtype=np.float32, count=1),
  823. units=pq.us, copy=False)[0]
  824. # segment_collection -- this is based off a segment_collection
  825. segments = self.__read_segment_list()
  826. # add the sampling period to each SpikeTrain
  827. for segment in segments:
  828. for spiketrain in segment.spiketrains:
  829. spiketrain.sampling_period = sampling_period
  830. return segments
  831. def __read_spike_fixed(self, numpts=40):
  832. """
  833. Read a spike with a fixed waveform length (40 time bins)
  834. -------------------------------------------
  835. Returns the time, waveform and trig2 value.
  836. The returned objects must be converted to a SpikeTrain then
  837. added to the Block.
  838. ID: 29079
  839. """
  840. # float32 -- spike time stamp in ms since start of SpikeTrain
  841. time = np.fromfile(self._fsrc, dtype=np.float32, count=1)
  842. # int8 * 40 -- spike shape -- use numpts for spike_var
  843. waveform = np.fromfile(self._fsrc, dtype=np.int8,
  844. count=numpts).reshape(1, 1, numpts)
  845. # uint8 -- point of return to noise
  846. trig2 = np.fromfile(self._fsrc, dtype=np.uint8, count=1)
  847. return time, waveform, trig2
  848. def __read_spike_fixed_old(self):
  849. """
  850. Read a spike with a fixed waveform length (40 time bins)
  851. This is an old version of the format. The time is stored as ints
  852. representing 1/25 ms time steps. It has no trigger information.
  853. -------------------------------------------
  854. Returns the time, waveform and trig2 value.
  855. The returned objects must be converted to a SpikeTrain then
  856. added to the Block.
  857. ID: 29081
  858. """
  859. # int32 -- spike time stamp in ms since start of SpikeTrain
  860. time = np.fromfile(self._fsrc, dtype=np.int32, count=1) / 25.
  861. time = time.astype(np.float32)
  862. # int8 * 40 -- spike shape
  863. # This needs to be a 3D array, one for each channel. BrainWare
  864. # only ever has a single channel per file.
  865. waveform = np.fromfile(self._fsrc, dtype=np.int8,
  866. count=40).reshape(1, 1, 40)
  867. # create a dummy trig2 value
  868. trig2 = np.array([-1], dtype=np.uint8)
  869. return time, waveform, trig2
  870. def __read_spike_var(self):
  871. """
  872. Read a spike with a variable waveform length
  873. -------------------------------------------
  874. Returns the time, waveform and trig2 value.
  875. The returned objects must be converted to a SpikeTrain then
  876. added to the Block.
  877. ID: 29115
  878. """
  879. # uint8 -- number of points in spike shape
  880. numpts = np.fromfile(self._fsrc, dtype=np.uint8, count=1)[0]
  881. # spike_fixed is the same as spike_var if you don't read the numpts
  882. # byte and set numpts = 40
  883. return self.__read_spike_fixed(numpts)
  884. def __read_spiketrain_indexed(self):
  885. """
  886. Read a SpikeTrain
  887. This is the same as __read_spiketrain_timestamped except it also
  888. contains the index of the Segment in the dam file.
  889. The index is stored as an annotation in the SpikeTrain.
  890. -------------------------------------------------
  891. Returns a SpikeTrain object with multiple spikes.
  892. The returned object must be added to the Block.
  893. ID: 29121
  894. """
  895. # int32 -- index of the analogsignalarray in corresponding .dam file
  896. dama_index = np.fromfile(self._fsrc, dtype=np.int32,
  897. count=1)[0]
  898. # spiketrain_timestamped -- this is based off a spiketrain_timestamped
  899. spiketrain = self.__read_spiketrain_timestamped()
  900. # add the property to the dict
  901. spiketrain.annotations['dama_index'] = dama_index
  902. return spiketrain
  903. def __read_spiketrain_timestamped(self):
  904. """
  905. Read a SpikeTrain
  906. This SpikeTrain contains a time stamp for when it was recorded
  907. The timestamp is stored as an annotation in the SpikeTrain.
  908. -------------------------------------------------
  909. Returns a SpikeTrain object with multiple spikes.
  910. The returned object must be added to the Block.
  911. ID: 29110
  912. """
  913. # float64 -- timeStamp (number of days since dec 30th 1899)
  914. timestamp = np.fromfile(self._fsrc, dtype=np.double, count=1)[0]
  915. # convert to datetime object
  916. timestamp = self._convert_timestamp(timestamp)
  917. # seq_list -- spike list
  918. # combine the spikes into a single SpikeTrain
  919. spiketrain = self._combine_spiketrains(self.__read_list())
  920. # add the timestamp
  921. spiketrain.annotations['timestamp'] = timestamp
  922. return spiketrain
  923. def __read_unit(self):
  924. """
  925. Read all SpikeTrains from a single Segment and Unit
  926. This is the same as __read_unit_unsorted except it also contains
  927. information on the spike sorting boundaries.
  928. ------------------------------------------------------------------
  929. Returns a single Unit and a list of SpikeTrains from that Unit and
  930. current Segment, in that order. The SpikeTrains must be returned since
  931. it is not possible to determine from the Unit which SpikeTrains are
  932. from the current Segment.
  933. The returned objects are already added to the Block. The SpikeTrains
  934. must be added to the current Segment.
  935. ID: 29116
  936. """
  937. # same as unsorted Unit
  938. unit, trains = self.__read_unit_unsorted()
  939. # float32 * 18 -- Unit boundaries (IEEE 32-bit floats)
  940. unit.annotations['boundaries'] = [np.fromfile(self._fsrc,
  941. dtype=np.float32,
  942. count=18)]
  943. # uint8 * 9 -- boolean values indicating elliptic feature boundary
  944. # dimensions
  945. unit.annotations['elliptic'] = [np.fromfile(self._fsrc,
  946. dtype=np.uint8,
  947. count=9)]
  948. return unit, trains
  949. def __read_unit_list(self):
  950. """
  951. A list of a list of Units
  952. -----------------------------------------------
  953. Returns a list of Units modified in the method.
  954. The returned objects are already added to the Block.
  955. No ID number: only called by other methods
  956. """
  957. # this is used to figure out which Units to return
  958. maxunit = 1
  959. # int16 -- number of time slices
  960. numelements = np.fromfile(self._fsrc, dtype=np.int16, count=1)[0]
  961. # {sequence} * numelements1 -- the number of lists of Units to read
  962. for i in range(numelements):
  963. # {skip} = byte * 2 (int16) -- skip 2 bytes
  964. self._fsrc.seek(2, 1)
  965. # double
  966. max_valid = np.fromfile(self._fsrc, dtype=np.double, count=1)[0]
  967. # int16 - the number of Units to read
  968. numunits = np.fromfile(self._fsrc, dtype=np.int16, count=1)[0]
  969. # update tha maximum Unit so far
  970. maxunit = max(maxunit, numunits + 1)
  971. # if there aren't enough Units, create them
  972. # remember we need to skip the UnassignedSpikes Unit
  973. if numunits > len(self._blk.groups) + 1:
  974. for ind1 in range(len(self._blk.groups), numunits + 1):
  975. unit = Group(name='unit%s' % ind1,
  976. file_origin=self._file_origin,
  977. elliptic=[], boundaries=[],
  978. timestamp=[], max_valid=[])
  979. self._blk.groups.append(unit)
  980. # {Block} * numelements -- Units
  981. for ind1 in range(numunits):
  982. # get the Unit with the given index
  983. # remember we need to skip the UnassignedSpikes Unit
  984. unit = self._blk.groups[ind1 + 1]
  985. # {skip} = byte * 2 (int16) -- skip 2 bytes
  986. self._fsrc.seek(2, 1)
  987. # int16 -- a multiplier for the elliptic and boundaries
  988. # properties
  989. numelements3 = np.fromfile(self._fsrc, dtype=np.int16,
  990. count=1)[0]
  991. # uint8 * 10 * numelements3 -- boolean values indicating
  992. # elliptic feature boundary dimensions
  993. elliptic = np.fromfile(self._fsrc, dtype=np.uint8,
  994. count=10 * numelements3)
  995. # float32 * 20 * numelements3 -- feature boundaries
  996. boundaries = np.fromfile(self._fsrc, dtype=np.float32,
  997. count=20 * numelements3)
  998. unit.annotations['elliptic'].append(elliptic)
  999. unit.annotations['boundaries'].append(boundaries)
  1000. unit.annotations['max_valid'].append(max_valid)
  1001. return self._blk.groups[1:maxunit]
  1002. def __read_unit_list_timestamped(self):
  1003. """
  1004. A list of a list of Units.
  1005. This is the same as __read_unit_list, except that it also has a
  1006. timestamp. This is added ad an annotation to all Units.
  1007. -----------------------------------------------
  1008. Returns a list of Units modified in the method.
  1009. The returned objects are already added to the Block.
  1010. ID: 29119
  1011. """
  1012. # double -- time zero (number of days since dec 30th 1899)
  1013. timestamp = np.fromfile(self._fsrc, dtype=np.double, count=1)[0]
  1014. # convert to to days since UNIX epoc time:
  1015. timestamp = self._convert_timestamp(timestamp)
  1016. # sorter -- this is based off a sorter
  1017. units = self.__read_unit_list()
  1018. for unit in units:
  1019. unit.annotations['timestamp'].append(timestamp)
  1020. return units
  1021. def __read_unit_old(self):
  1022. """
  1023. Read all SpikeTrains from a single Segment and Unit
  1024. This is the same as __read_unit_unsorted except it also contains
  1025. information on the spike sorting boundaries.
  1026. This is an old version of the format that used 48-bit floating-point
  1027. numbers for the boundaries. These cannot easily be read and so are
  1028. skipped.
  1029. ------------------------------------------------------------------
  1030. Returns a single Unit and a list of SpikeTrains from that Unit and
  1031. current Segment, in that order. The SpikeTrains must be returned since
  1032. it is not possible to determine from the Unit which SpikeTrains are
  1033. from the current Segment.
  1034. The returned objects are already added to the Block. The SpikeTrains
  1035. must be added to the current Segment.
  1036. ID: 29107
  1037. """
  1038. # same as Unit
  1039. unit, trains = self.__read_unit_unsorted()
  1040. # bytes * 108 (float48 * 18) -- Unit boundaries (48-bit floating
  1041. # point numbers are not supported so we skip them)
  1042. self._fsrc.seek(108, 1)
  1043. # uint8 * 9 -- boolean values indicating elliptic feature boundary
  1044. # dimensions
  1045. unit.annotations['elliptic'] = np.fromfile(self._fsrc, dtype=np.uint8,
  1046. count=9).tolist()
  1047. return unit, trains
  1048. def __read_unit_unsorted(self):
  1049. """
  1050. Read all SpikeTrains from a single Segment and Unit
  1051. This does not contain Unit boundaries.
  1052. ------------------------------------------------------------------
  1053. Returns a single Unit and a list of SpikeTrains from that Unit and
  1054. current Segment, in that order. The SpikeTrains must be returned since
  1055. it is not possible to determine from the Unit which SpikeTrains are
  1056. from the current Segment.
  1057. The returned objects are already added to the Block. The SpikeTrains
  1058. must be added to the current Segment.
  1059. ID: 29084
  1060. """
  1061. # {skip} = bytes * 2 (uint16) -- skip two bytes
  1062. self._fsrc.seek(2, 1)
  1063. # uint16 -- number of characters in next string
  1064. numchars = np.fromfile(self._fsrc, dtype=np.uint16, count=1).item()
  1065. # char * numchars -- ID string of Unit
  1066. name = self.__read_str(numchars)
  1067. # int32 -- SpikeTrain length in ms
  1068. # int32 * 4 -- response and spon period boundaries
  1069. parts = np.fromfile(self._fsrc, dtype=np.int32, count=5)
  1070. t_stop = pq.Quantity(parts[0].astype('float32'),
  1071. units=pq.ms, copy=False)
  1072. respwin = parts[1:]
  1073. # (data_obj) -- list of SpikeTrains
  1074. spikeslists = self._read_by_id()
  1075. # use the Unit if it already exists, otherwise create it
  1076. if name in self._unitdict:
  1077. unit = self._unitdict[name]
  1078. else:
  1079. unit = Group(name=name, file_origin=self._file_origin,
  1080. elliptic=[], boundaries=[], timestamp=[], max_valid=[])
  1081. self._blk.groups.append(unit)
  1082. self._unitdict[name] = unit
  1083. # convert the individual spikes to SpikeTrains and add them to the Unit
  1084. trains = [self._combine_spiketrains(spikes) for spikes in spikeslists]
  1085. unit.spiketrains.extend(trains)
  1086. for train in trains:
  1087. train.t_stop = t_stop.copy()
  1088. train.annotations['respwin'] = respwin.copy()
  1089. return unit, trains
  1090. def __skip_information(self):
  1091. """
  1092. Read an information sequence.
  1093. This is data sequence is skipped both here and in the Matlab reference
  1094. implementation.
  1095. ----------------------
  1096. Returns an empty list
  1097. Nothing is created so nothing is added to the Block.
  1098. ID: 29113
  1099. """
  1100. # {skip} char * 34 -- display information
  1101. self._fsrc.seek(34, 1)
  1102. return []
  1103. def __skip_information_old(self):
  1104. """
  1105. Read an information sequence
  1106. This is data sequence is skipped both here and in the Matlab reference
  1107. implementation
  1108. This is an old version of the format
  1109. ----------------------
  1110. Returns an empty list.
  1111. Nothing is created so nothing is added to the Block.
  1112. ID: 29100
  1113. """
  1114. # {skip} char * 4 -- display information
  1115. self._fsrc.seek(4, 1)
  1116. return []
  1117. # This dictionary maps the numeric data sequence ID codes to the data
  1118. # sequence reading functions.
  1119. #
  1120. # Since functions are first-class objects in Python, the functions returned
  1121. # from this dictionary are directly callable.
  1122. #
  1123. # If new data sequence ID codes are added in the future please add the code
  1124. # here in numeric order and the method above in alphabetical order
  1125. #
  1126. # The naming of any private method may change at any time
  1127. _ID_DICT = {29079: __read_spike_fixed,
  1128. 29081: __read_spike_fixed_old,
  1129. 29082: __read_list,
  1130. 29083: __read_list,
  1131. 29084: __read_unit_unsorted,
  1132. 29091: __read_list,
  1133. 29093: __read_list,
  1134. 29099: __read_annotations_old,
  1135. 29100: __skip_information_old,
  1136. 29106: __read_segment,
  1137. 29107: __read_unit_old,
  1138. 29109: __read_annotations,
  1139. 29110: __read_spiketrain_timestamped,
  1140. 29112: __read_segment_list,
  1141. 29113: __skip_information,
  1142. 29114: __read_segment_list_var,
  1143. 29115: __read_spike_var,
  1144. 29116: __read_unit,
  1145. 29117: __read_segment_list_v8,
  1146. 29119: __read_unit_list_timestamped,
  1147. 29120: __read_segment_list_v9,
  1148. 29121: __read_spiketrain_indexed
  1149. }
  1150. def convert_brainwaresrc_timestamp(timestamp,
  1151. start_date=datetime(1899, 12, 30)):
  1152. """
  1153. convert_brainwaresrc_timestamp(timestamp, start_date) - convert a timestamp
  1154. in brainware src file units to a python datetime object.
  1155. start_date defaults to 1899.12.30 (ISO format), which is the start date
  1156. used by all BrainWare SRC data Blocks so far. If manually specified
  1157. it should be a datetime object or any other object that can be added
  1158. to a timedelta object.
  1159. """
  1160. # datetime + timedelta = datetime again.
  1161. return start_date + timedelta(days=timestamp)
  1162. if __name__ == '__main__':
  1163. # run this when calling the file directly as a benchmark
  1164. from neo.test.iotest.test_brainwaresrcio import FILES_TO_TEST
  1165. from neo.test.iotest.common_io_test import url_for_tests
  1166. from neo.test.iotest.tools import (create_local_temp_dir,
  1167. download_test_file,
  1168. get_test_file_full_path,
  1169. make_all_directories)
  1170. shortname = BrainwareSrcIO.__name__.lower().strip('io')
  1171. local_test_dir = create_local_temp_dir(shortname)
  1172. url = url_for_tests + shortname
  1173. FILES_TO_TEST.remove('long_170s_1rep_1clust_ch2.src')
  1174. make_all_directories(FILES_TO_TEST, local_test_dir)
  1175. download_test_file(FILES_TO_TEST, local_test_dir, url)
  1176. for path in get_test_file_full_path(ioclass=BrainwareSrcIO,
  1177. filename=FILES_TO_TEST,
  1178. directory=local_test_dir):
  1179. ioobj = BrainwareSrcIO(path)
  1180. ioobj.read_all_blocks(lazy=False)