Scheduled service maintenance on November 22


On Friday, November 22, 2024, between 06:00 CET and 18:00 CET, GIN services will undergo planned maintenance. Extended service interruptions should be expected. We will try to keep downtimes to a minimum, but recommend that users avoid critical tasks, large data uploads, or DOI requests during this time.

We apologize for any inconvenience.

tdtio.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. # -*- coding: utf-8 -*-
  2. """
  3. Class for reading data from from Tucker Davis TTank format.
  4. Terminology:
  5. TDT hold data with tanks (actually a directory). And tanks hold sub block
  6. (sub directories).
  7. Tanks correspond to neo.Block and tdt block correspond to neo.Segment.
  8. Note the name Block is ambiguous because it does not refer to same thing in TDT
  9. terminology and neo.
  10. Depend on:
  11. Supported : Read
  12. Author: sgarcia
  13. """
  14. import os
  15. import struct
  16. import sys
  17. import numpy as np
  18. import quantities as pq
  19. import itertools
  20. from neo.io.baseio import BaseIO
  21. from neo.core import Block, Segment, AnalogSignal, SpikeTrain, Event
  22. from neo.io.tools import iteritems
  23. PY3K = (sys.version_info[0] == 3)
  24. def get_chunks(sizes, offsets, big_array):
  25. # offsets are octect count
  26. # sizes are not!!
  27. # so need this (I really do not knwo why...):
  28. sizes = (sizes -10) * 4 #
  29. all = np.concatenate([ big_array[o:o+s] for s, o in itertools.izip(sizes, offsets) ])
  30. return all
  31. class TdtIO(BaseIO):
  32. """
  33. Class for reading data from from Tucker Davis TTank format.
  34. Usage:
  35. >>> from neo import io
  36. >>> r = io.TdtIO(dirname='aep_05')
  37. >>> bl = r.read_block(lazy=False, cascade=True)
  38. >>> print bl.segments
  39. [<neo.core.segment.Segment object at 0x1060a4d10>]
  40. >>> print bl.segments[0].analogsignals
  41. [<AnalogSignal(array([ 2.18811035, 2.19726562, 2.21252441, ...,
  42. 1.33056641, 1.3458252 , 1.3671875 ], dtype=float32) * pA,
  43. [0.0 s, 191.2832 s], sampling rate: 10000.0 Hz)>]
  44. >>> print bl.segments[0].events
  45. []
  46. """
  47. is_readable = True
  48. is_writable = False
  49. supported_objects = [Block, Segment , AnalogSignal, Event]
  50. readable_objects = [Block, Segment]
  51. writeable_objects = []
  52. has_header = False
  53. is_streameable = False
  54. read_params = {
  55. Block : [],
  56. Segment : []
  57. }
  58. write_params = None
  59. name = 'TDT'
  60. extensions = [ ]
  61. mode = 'dir'
  62. def __init__(self , dirname=None) :
  63. """
  64. **Arguments**
  65. Arguments:
  66. dirname: path of the TDT tank (a directory)
  67. """
  68. BaseIO.__init__(self)
  69. self.dirname = dirname
  70. if self.dirname.endswith('/'):
  71. self.dirname = self.dirname[:-1]
  72. def read_segment(self, blockname=None, lazy=False, cascade=True, sortname=''):
  73. """
  74. Read a single segment from the tank. Note that TDT blocks are Neo
  75. segments, and TDT tanks are Neo blocks, so here the 'blockname' argument
  76. refers to the TDT block's name, which will be the Neo segment name.
  77. 'sortname' is used to specify the external sortcode generated by offline spike sorting.
  78. if sortname=='PLX', there should be a ./sort/PLX/*.SortResult file in the tdt block,
  79. which stores the sortcode for every spike; defaults to '', which uses the original online sort
  80. """
  81. if not blockname:
  82. blockname = os.listdir(self.dirname)[0]
  83. if blockname == 'TempBlk': return None
  84. if not self.is_tdtblock(blockname): return None # if not a tdt block
  85. subdir = os.path.join(self.dirname, blockname)
  86. if not os.path.isdir(subdir): return None
  87. seg = Segment(name=blockname)
  88. tankname = os.path.basename(self.dirname)
  89. #TSQ is the global index
  90. tsq_filename = os.path.join(subdir, tankname+'_'+blockname+'.tsq')
  91. dt = [('size','int32'),
  92. ('evtype','int32'),
  93. ('code','S4'),
  94. ('channel','uint16'),
  95. ('sortcode','uint16'),
  96. ('timestamp','float64'),
  97. ('eventoffset','int64'),
  98. ('dataformat','int32'),
  99. ('frequency','float32'),
  100. ]
  101. tsq = np.fromfile(tsq_filename, dtype=dt)
  102. #0x8801: 'EVTYPE_MARK' give the global_start
  103. global_t_start = tsq[tsq['evtype']==0x8801]['timestamp'][0]
  104. #TEV is the old data file
  105. try:
  106. tev_filename = os.path.join(subdir, tankname+'_'+blockname+'.tev')
  107. #tev_array = np.memmap(tev_filename, mode = 'r', dtype = 'uint8') # if memory problem use this instead
  108. tev_array = np.fromfile(tev_filename, dtype='uint8')
  109. except IOError:
  110. tev_filename = None
  111. #if there exists an external sortcode in ./sort/[sortname]/*.SortResult (generated after offline sortting)
  112. sortresult_filename = None
  113. if sortname is not '':
  114. try:
  115. for file in os.listdir(os.path.join(subdir, 'sort', sortname)):
  116. if file.endswith(".SortResult"):
  117. sortresult_filename = os.path.join(subdir, 'sort', sortname, file)
  118. # get new sortcode
  119. newsorcode = np.fromfile(sortresult_filename,'int8')[1024:] # the first 1024 byte is file header
  120. # update the sort code with the info from this file
  121. tsq['sortcode'][1:-1]=newsorcode
  122. # print('sortcode updated')
  123. break
  124. except OSError:
  125. sortresult_filename = None
  126. except IOError:
  127. sortresult_filename = None
  128. for type_code, type_label in tdt_event_type:
  129. mask1 = tsq['evtype']==type_code
  130. codes = np.unique(tsq[mask1]['code'])
  131. for code in codes:
  132. mask2 = mask1 & (tsq['code']==code)
  133. channels = np.unique(tsq[mask2]['channel'])
  134. for channel in channels:
  135. mask3 = mask2 & (tsq['channel']==channel)
  136. if type_label in ['EVTYPE_STRON', 'EVTYPE_STROFF']:
  137. if lazy:
  138. times = [ ]*pq.s
  139. labels = np.array([ ], dtype=str)
  140. else:
  141. times = (tsq[mask3]['timestamp'] - global_t_start) * pq.s
  142. labels = tsq[mask3]['eventoffset'].view('float64').astype('S')
  143. ea = Event(times=times,
  144. name=code,
  145. channel_index=int(channel),
  146. labels=labels)
  147. if lazy:
  148. ea.lazy_shape = np.sum(mask3)
  149. seg.events.append(ea)
  150. elif type_label == 'EVTYPE_SNIP':
  151. sortcodes = np.unique(tsq[mask3]['sortcode'])
  152. for sortcode in sortcodes:
  153. mask4 = mask3 & (tsq['sortcode']==sortcode)
  154. nb_spike = np.sum(mask4)
  155. sr = tsq[mask4]['frequency'][0]
  156. waveformsize = tsq[mask4]['size'][0]-10
  157. if lazy:
  158. times = [ ]*pq.s
  159. waveforms = None
  160. else:
  161. times = (tsq[mask4]['timestamp'] - global_t_start) * pq.s
  162. dt = np.dtype(data_formats[ tsq[mask3]['dataformat'][0]])
  163. waveforms = get_chunks(tsq[mask4]['size'],tsq[mask4]['eventoffset'], tev_array).view(dt)
  164. waveforms = waveforms.reshape(nb_spike, -1, waveformsize)
  165. waveforms = waveforms * pq.mV
  166. if nb_spike > 0:
  167. # t_start = (tsq['timestamp'][0] - global_t_start) * pq.s # this hould work but not
  168. t_start = 0 *pq.s
  169. t_stop = (tsq['timestamp'][-1] - global_t_start) * pq.s
  170. else:
  171. t_start = 0 *pq.s
  172. t_stop = 0 *pq.s
  173. st = SpikeTrain(times = times,
  174. name = 'Chan{0} Code{1}'.format(channel,sortcode),
  175. t_start = t_start,
  176. t_stop = t_stop,
  177. waveforms = waveforms,
  178. left_sweep = waveformsize/2./sr * pq.s,
  179. sampling_rate = sr * pq.Hz,
  180. )
  181. st.annotate(channel_index=channel)
  182. if lazy:
  183. st.lazy_shape = nb_spike
  184. seg.spiketrains.append(st)
  185. elif type_label == 'EVTYPE_STREAM':
  186. dt = np.dtype(data_formats[ tsq[mask3]['dataformat'][0]])
  187. shape = np.sum(tsq[mask3]['size']-10)
  188. sr = tsq[mask3]['frequency'][0]
  189. if lazy:
  190. signal = [ ]
  191. else:
  192. if PY3K:
  193. signame = code.decode('ascii')
  194. else:
  195. signame = code
  196. sev_filename = os.path.join(subdir, tankname+'_'+blockname+'_'+signame+'_ch'+str(channel)+'.sev')
  197. try:
  198. #sig_array = np.memmap(sev_filename, mode = 'r', dtype = 'uint8') # if memory problem use this instead
  199. sig_array = np.fromfile(sev_filename, dtype='uint8')
  200. except IOError:
  201. sig_array = tev_array
  202. signal = get_chunks(tsq[mask3]['size'],tsq[mask3]['eventoffset'], sig_array).view(dt)
  203. anasig = AnalogSignal(signal = signal* pq.V,
  204. name = '{0} {1}'.format(code, channel),
  205. sampling_rate = sr * pq.Hz,
  206. t_start = (tsq[mask3]['timestamp'][0] - global_t_start) * pq.s,
  207. channel_index = int(channel)
  208. )
  209. if lazy:
  210. anasig.lazy_shape = shape
  211. seg.analogsignals.append(anasig)
  212. return seg
  213. def read_block(self, lazy=False, cascade=True, sortname=''):
  214. bl = Block()
  215. tankname = os.path.basename(self.dirname)
  216. bl.file_origin = tankname
  217. if not cascade : return bl
  218. for blockname in os.listdir(self.dirname):
  219. if self.is_tdtblock(blockname): # if the folder is a tdt block
  220. seg = self.read_segment(blockname, lazy, cascade, sortname)
  221. bl.segments.append(seg)
  222. bl.create_many_to_one_relationship()
  223. return bl
  224. # to determine if this folder is a TDT block, based on the extension of the files inside it
  225. # to deal with unexpected files in the tank, e.g. .DS_Store on Mac machines
  226. def is_tdtblock(self, blockname):
  227. file_ext = list()
  228. blockpath = os.path.join(self.dirname, blockname) # get block path
  229. if os.path.isdir(blockpath):
  230. for file in os.listdir( blockpath ): # for every file, get extension, convert to lowercase and append
  231. file_ext.append( os.path.splitext( file )[1].lower() )
  232. file_ext = set(file_ext)
  233. tdt_ext = set(['.tbk', '.tdx', '.tev', '.tsq'])
  234. if file_ext >= tdt_ext: # if containing all the necessary files
  235. return True
  236. else:
  237. return False
  238. tdt_event_type = [
  239. #(0x0,'EVTYPE_UNKNOWN'),
  240. (0x101, 'EVTYPE_STRON'),
  241. (0x102,'EVTYPE_STROFF'),
  242. #(0x201,'EVTYPE_SCALER'),
  243. (0x8101, 'EVTYPE_STREAM'),
  244. (0x8201, 'EVTYPE_SNIP'),
  245. #(0x8801, 'EVTYPE_MARK'),
  246. ]
  247. data_formats = {
  248. 0 : np.float32,
  249. 1 : np.int32,
  250. 2 : np.int16,
  251. 3 : np.int8,
  252. 4 : np.float64,
  253. }