brainwaredamio.py 7.9 KB

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