brainwaresrcio.py 58 KB

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