brainwaredamio.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Class for reading from Brainware DAM files
  4. DAM files are binary files for holding raw data. They are broken up into
  5. sequence of Segments, each containing a single raw trace and parameters.
  6. The DAM file does NOT contain a sampling rate, nor can it be reliably
  7. calculated from any of the parameters. You can calculate it from
  8. the "sweep length" attribute if it is present, but it isn't always present.
  9. It is more reliable to get it from the corresponding SRC file or F32 file if
  10. you have one.
  11. The DAM file also does not divide up data into Blocks, so only a single
  12. Block is returned..
  13. Brainware was developed by Dr. Jan Schnupp and is availabe from
  14. Tucker Davis Technologies, Inc.
  15. http://www.tdt.com/downloads.htm
  16. Neither Dr. Jan Schnupp nor Tucker Davis Technologies, Inc. had any part in the
  17. development of this code
  18. The code is implemented with the permission of Dr. Jan Schnupp
  19. Author: Todd Jennings
  20. '''
  21. # needed for python 3 compatibility
  22. from __future__ import absolute_import, division, print_function
  23. # import needed core python modules
  24. import os
  25. import os.path
  26. # numpy and quantities are already required by neo
  27. import numpy as np
  28. import quantities as pq
  29. # needed core neo modules
  30. from neo.core import (AnalogSignal, Block,
  31. ChannelIndex, Segment)
  32. # need to subclass BaseIO
  33. from neo.io.baseio import BaseIO
  34. class BrainwareDamIO(BaseIO):
  35. """
  36. Class for reading Brainware raw data files with the extension '.dam'.
  37. The read_block method returns the first Block of the file. It will
  38. automatically close the file after reading.
  39. The read method is the same as read_block.
  40. Note:
  41. The file format does not contain a sampling rate. The sampling rate
  42. is set to 1 Hz, but this is arbitrary. If you have a corresponding .src
  43. or .f32 file, you can get the sampling rate from that. It may also be
  44. possible to infer it from the attributes, such as "sweep length", if
  45. present.
  46. Usage:
  47. >>> from neo.io.brainwaredamio import BrainwareDamIO
  48. >>> damfile = BrainwareDamIO(filename='multi_500ms_mulitrep_ch1.dam')
  49. >>> blk1 = damfile.read()
  50. >>> blk2 = damfile.read_block()
  51. >>> print blk1.segments
  52. >>> print blk1.segments[0].analogsignals
  53. >>> print blk1.units
  54. >>> print blk1.units[0].name
  55. >>> print blk2
  56. >>> print blk2[0].segments
  57. """
  58. is_readable = True # This class can only read data
  59. is_writable = False # write is not supported
  60. # This class is able to directly or indirectly handle the following objects
  61. # You can notice that this greatly simplifies the full Neo object hierarchy
  62. supported_objects = [Block, ChannelIndex,
  63. Segment, AnalogSignal]
  64. readable_objects = [Block]
  65. writeable_objects = []
  66. has_header = False
  67. is_streameable = False
  68. # This is for GUI stuff: a definition for parameters when reading.
  69. # This dict should be keyed by object (`Block`). Each entry is a list
  70. # of tuple. The first entry in each tuple is the parameter name. The
  71. # second entry is a dict with keys 'value' (for default value),
  72. # and 'label' (for a descriptive name).
  73. # Note that if the highest-level object requires parameters,
  74. # common_io_test will be skipped.
  75. read_params = {Block: []}
  76. # do not support write so no GUI stuff
  77. write_params = None
  78. name = 'Brainware DAM File'
  79. extensions = ['dam']
  80. mode = 'file'
  81. def __init__(self, filename=None):
  82. '''
  83. Arguments:
  84. filename: the filename
  85. '''
  86. BaseIO.__init__(self)
  87. self._path = filename
  88. self._filename = os.path.basename(filename)
  89. self._fsrc = None
  90. def read(self, lazy=False, **kargs):
  91. '''
  92. Reads raw data file "fname" generated with BrainWare
  93. '''
  94. assert not lazy, 'Do not support lazy'
  95. return self.read_block(lazy=lazy)
  96. def read_block(self, lazy=False, **kargs):
  97. '''
  98. Reads a block from the raw data file "fname" generated
  99. with BrainWare
  100. '''
  101. assert not lazy, 'Do not support lazy'
  102. # there are no keyargs implemented to so far. If someone tries to pass
  103. # them they are expecting them to do something or making a mistake,
  104. # neither of which should pass silently
  105. if kargs:
  106. raise NotImplementedError('This method does not have any '
  107. 'arguments implemented yet')
  108. self._fsrc = None
  109. block = Block(file_origin=self._filename)
  110. # create the objects to store other objects
  111. chx = ChannelIndex(file_origin=self._filename,
  112. channel_ids=np.array([1]),
  113. index=np.array([0]),
  114. channel_names=np.array(['Chan1'], dtype='S'))
  115. # load objects into their containers
  116. block.channel_indexes.append(chx)
  117. # open the file
  118. with open(self._path, 'rb') as fobject:
  119. # while the file is not done keep reading segments
  120. while True:
  121. seg = self._read_segment(fobject)
  122. # if there are no more Segments, stop
  123. if not seg:
  124. break
  125. # store the segment and signals
  126. seg.analogsignals[0].channel_index = chx
  127. block.segments.append(seg)
  128. # remove the file object
  129. self._fsrc = None
  130. block.create_many_to_one_relationship()
  131. return block
  132. # -------------------------------------------------------------------------
  133. # -------------------------------------------------------------------------
  134. # IMPORTANT!!!
  135. # These are private methods implementing the internal reading mechanism.
  136. # Due to the way BrainWare DAM files are structured, they CANNOT be used
  137. # on their own. Calling these manually will almost certainly alter your
  138. # position in the file in an unrecoverable manner, whether they throw
  139. # an exception or not.
  140. # -------------------------------------------------------------------------
  141. # -------------------------------------------------------------------------
  142. def _read_segment(self, fobject):
  143. '''
  144. Read a single segment with a single analogsignal
  145. Returns the segment or None if there are no more segments
  146. '''
  147. try:
  148. # float64 -- start time of the AnalogSignal
  149. t_start = np.fromfile(fobject, dtype=np.float64, count=1)[0]
  150. except IndexError:
  151. # if there are no more Segments, return
  152. return False
  153. # int16 -- index of the stimulus parameters
  154. seg_index = np.fromfile(fobject, dtype=np.int16, count=1)[0].tolist()
  155. # int16 -- number of stimulus parameters
  156. numelements = np.fromfile(fobject, dtype=np.int16, count=1)[0]
  157. # read the name strings for the stimulus parameters
  158. paramnames = []
  159. for _ in range(numelements):
  160. # unit8 -- the number of characters in the string
  161. numchars = np.fromfile(fobject, dtype=np.uint8, count=1)[0]
  162. # char * numchars -- a single name string
  163. name = np.fromfile(fobject, dtype=np.uint8, count=numchars)
  164. # exclude invalid characters
  165. name = str(name[name >= 32].view('c').tostring())
  166. # add the name to the list of names
  167. paramnames.append(name)
  168. # float32 * numelements -- the values for the stimulus parameters
  169. paramvalues = np.fromfile(fobject, dtype=np.float32, count=numelements)
  170. # combine parameter names and the parameters as a dict
  171. params = dict(zip(paramnames, paramvalues))
  172. # int32 -- the number elements in the AnalogSignal
  173. numpts = np.fromfile(fobject, dtype=np.int32, count=1)[0]
  174. # int16 * numpts -- the AnalogSignal itself
  175. signal = np.fromfile(fobject, dtype=np.int16, count=numpts)
  176. sig = AnalogSignal(signal.astype(np.float) * pq.mV,
  177. t_start=t_start * pq.d,
  178. file_origin=self._filename,
  179. sampling_period=1. * pq.s,
  180. copy=False)
  181. # Note: setting the sampling_period to 1 s is arbitrary
  182. # load the AnalogSignal and parameters into a new Segment
  183. seg = Segment(file_origin=self._filename,
  184. index=seg_index,
  185. **params)
  186. seg.analogsignals = [sig]
  187. return seg