axonio.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  1. # -*- coding: utf-8 -*-
  2. """
  3. Class for reading data from pCLAMP and AxoScope
  4. files (.abf version 1 and 2), developed by Molecular device/Axon technologies.
  5. - abf = Axon binary file
  6. - atf is a text file based format from axon that could be
  7. read by AsciiIO (but this file is less efficient.)
  8. This code is a port of abfload and abf2load
  9. written in Matlab (BSD-2-Clause licence) by :
  10. - Copyright (c) 2009, Forrest Collman, fcollman@princeton.edu
  11. - Copyright (c) 2004, Harald Hentschke
  12. and available here:
  13. http://www.mathworks.com/matlabcentral/fileexchange/22114-abf2load
  14. Information on abf 1 and 2 formats is available here:
  15. http://www.moleculardevices.com/pages/software/developer_info.html
  16. This file supports the old (ABF1) and new (ABF2) format.
  17. ABF1 (clampfit <=9) and ABF2 (clampfit >10)
  18. All possible mode are possible :
  19. - event-driven variable-length mode 1 -> return several Segments per Block
  20. - event-driven fixed-length mode 2 or 5 -> return several Segments
  21. - gap free mode -> return one (or sevral) Segment in the Block
  22. Supported : Read
  23. Author: sgarcia, jnowacki
  24. Note: j.s.nowacki@gmail.com has a C++ library with SWIG bindings which also
  25. reads abf files - would be good to cross-check
  26. """
  27. import struct
  28. import datetime
  29. import os
  30. from io import open, BufferedReader
  31. import numpy as np
  32. import quantities as pq
  33. from neo.io.baseio import BaseIO
  34. from neo.core import Block, Segment, AnalogSignal, Event
  35. from neo.io.tools import iteritems
  36. class StructFile(BufferedReader):
  37. def read_f(self, fmt, offset=None):
  38. if offset is not None:
  39. self.seek(offset)
  40. return struct.unpack(fmt, self.read(struct.calcsize(fmt)))
  41. def write_f(self, fmt, offset=None, *args):
  42. if offset is not None:
  43. self.seek(offset)
  44. self.write(struct.pack(fmt, *args))
  45. def reformat_integer_v1(data, nbchannel, header):
  46. """
  47. reformat when dtype is int16 for ABF version 1
  48. """
  49. chans = [chan_num for chan_num in
  50. header['nADCSamplingSeq'] if chan_num >= 0]
  51. for n, i in enumerate(chans[:nbchannel]): # respect SamplingSeq
  52. data[:, n] /= header['fInstrumentScaleFactor'][i]
  53. data[:, n] /= header['fSignalGain'][i]
  54. data[:, n] /= header['fADCProgrammableGain'][i]
  55. if header['nTelegraphEnable'][i]:
  56. data[:, n] /= header['fTelegraphAdditGain'][i]
  57. data[:, n] *= header['fADCRange']
  58. data[:, n] /= header['lADCResolution']
  59. data[:, n] += header['fInstrumentOffset'][i]
  60. data[:, n] -= header['fSignalOffset'][i]
  61. def reformat_integer_v2(data, nbchannel, header):
  62. """
  63. reformat when dtype is int16 for ABF version 2
  64. """
  65. for i in range(nbchannel):
  66. data[:, i] /= header['listADCInfo'][i]['fInstrumentScaleFactor']
  67. data[:, i] /= header['listADCInfo'][i]['fSignalGain']
  68. data[:, i] /= header['listADCInfo'][i]['fADCProgrammableGain']
  69. if header['listADCInfo'][i]['nTelegraphEnable']:
  70. data[:, i] /= header['listADCInfo'][i]['fTelegraphAdditGain']
  71. data[:, i] *= header['protocol']['fADCRange']
  72. data[:, i] /= header['protocol']['lADCResolution']
  73. data[:, i] += header['listADCInfo'][i]['fInstrumentOffset']
  74. data[:, i] -= header['listADCInfo'][i]['fSignalOffset']
  75. def clean_string(s):
  76. s = s.rstrip(b'\x00')
  77. s = s.rstrip(b' ')
  78. return s
  79. class AxonIO(BaseIO):
  80. """
  81. Class for reading data from pCLAMP and AxoScope
  82. files (.abf version 1 and 2), developed by Molecular Device/Axon Technologies.
  83. Usage:
  84. >>> from neo import io
  85. >>> r = io.AxonIO(filename='File_axon_1.abf')
  86. >>> bl = r.read_block(lazy=False, cascade=True)
  87. >>> print bl.segments
  88. [<neo.core.segment.Segment object at 0x105516fd0>]
  89. >>> print bl.segments[0].analogsignals
  90. [<AnalogSignal(array([ 2.18811035, 2.19726562, 2.21252441, ...,
  91. 1.33056641, 1.3458252 , 1.3671875 ], dtype=float32) * pA,
  92. [0.0 s, 191.2832 s], sampling rate: 10000.0 Hz)>]
  93. >>> print bl.segments[0].events
  94. []
  95. """
  96. is_readable = True
  97. is_writable = False
  98. supported_objects = [Block, Segment, AnalogSignal, Event]
  99. readable_objects = [Block]
  100. writeable_objects = []
  101. has_header = False
  102. is_streameable = False
  103. read_params = {Block: []}
  104. write_params = None
  105. name = 'Axon'
  106. extensions = ['abf']
  107. mode = 'file'
  108. def __init__(self, filename=None):
  109. """
  110. This class read a abf file.
  111. Arguments:
  112. filename : the filename to read
  113. """
  114. BaseIO.__init__(self)
  115. self.filename = filename
  116. def read_block(self, lazy=False, cascade=True):
  117. header = self.read_header()
  118. version = header['fFileVersionNumber']
  119. bl = Block()
  120. bl.file_origin = os.path.basename(self.filename)
  121. bl.annotate(abf_version=str(version))
  122. # date and time
  123. if version < 2.:
  124. YY = 1900
  125. MM = 1
  126. DD = 1
  127. hh = int(header['lFileStartTime'] / 3600.)
  128. mm = int((header['lFileStartTime'] - hh * 3600) / 60)
  129. ss = header['lFileStartTime'] - hh * 3600 - mm * 60
  130. ms = int(np.mod(ss, 1) * 1e6)
  131. ss = int(ss)
  132. elif version >= 2.:
  133. YY = int(header['uFileStartDate'] / 10000)
  134. MM = int((header['uFileStartDate'] - YY * 10000) / 100)
  135. DD = int(header['uFileStartDate'] - YY * 10000 - MM * 100)
  136. hh = int(header['uFileStartTimeMS'] / 1000. / 3600.)
  137. mm = int((header['uFileStartTimeMS'] / 1000. - hh * 3600) / 60)
  138. ss = header['uFileStartTimeMS'] / 1000. - hh * 3600 - mm * 60
  139. ms = int(np.mod(ss, 1) * 1e6)
  140. ss = int(ss)
  141. bl.rec_datetime = datetime.datetime(YY, MM, DD, hh, mm, ss, ms)
  142. if not cascade:
  143. return bl
  144. # file format
  145. if header['nDataFormat'] == 0:
  146. dt = np.dtype('i2')
  147. elif header['nDataFormat'] == 1:
  148. dt = np.dtype('f4')
  149. if version < 2.:
  150. nbchannel = header['nADCNumChannels']
  151. head_offset = header['lDataSectionPtr'] * BLOCKSIZE + header[
  152. 'nNumPointsIgnored'] * dt.itemsize
  153. totalsize = header['lActualAcqLength']
  154. elif version >= 2.:
  155. nbchannel = header['sections']['ADCSection']['llNumEntries']
  156. head_offset = header['sections']['DataSection'][
  157. 'uBlockIndex'] * BLOCKSIZE
  158. totalsize = header['sections']['DataSection']['llNumEntries']
  159. data = np.memmap(self.filename, dt, 'r',
  160. shape=(totalsize,), offset=head_offset)
  161. # 3 possible modes
  162. if version < 2.:
  163. mode = header['nOperationMode']
  164. elif version >= 2.:
  165. mode = header['protocol']['nOperationMode']
  166. if (mode == 1) or (mode == 2) or (mode == 5) or (mode == 3):
  167. # event-driven variable-length mode (mode 1)
  168. # event-driven fixed-length mode (mode 2 or 5)
  169. # gap free mode (mode 3) can be in several episodes
  170. # read sweep pos
  171. if version < 2.:
  172. nbepisod = header['lSynchArraySize']
  173. offset_episode = header['lSynchArrayPtr'] * BLOCKSIZE
  174. elif version >= 2.:
  175. nbepisod = header['sections']['SynchArraySection'][
  176. 'llNumEntries']
  177. offset_episode = header['sections']['SynchArraySection'][
  178. 'uBlockIndex'] * BLOCKSIZE
  179. if nbepisod > 0:
  180. episode_array = np.memmap(
  181. self.filename, [('offset', 'i4'), ('len', 'i4')], 'r',
  182. shape=nbepisod, offset=offset_episode)
  183. else:
  184. episode_array = np.empty(1, [('offset', 'i4'), ('len', 'i4')])
  185. episode_array[0]['len'] = data.size
  186. episode_array[0]['offset'] = 0
  187. # sampling_rate
  188. if version < 2.:
  189. sampling_rate = 1. / (header['fADCSampleInterval'] *
  190. nbchannel * 1.e-6) * pq.Hz
  191. elif version >= 2.:
  192. sampling_rate = 1.e6 / \
  193. header['protocol']['fADCSequenceInterval'] * pq.Hz
  194. # construct block
  195. # one sweep = one segment in a block
  196. pos = 0
  197. for j in range(episode_array.size):
  198. seg = Segment(index=j)
  199. length = episode_array[j]['len']
  200. if version < 2.:
  201. fSynchTimeUnit = header['fSynchTimeUnit']
  202. elif version >= 2.:
  203. fSynchTimeUnit = header['protocol']['fSynchTimeUnit']
  204. if (fSynchTimeUnit != 0) and (mode == 1):
  205. length /= fSynchTimeUnit
  206. if not lazy:
  207. subdata = data[pos:pos+length]
  208. subdata = subdata.reshape((int(subdata.size/nbchannel),
  209. nbchannel)).astype('f')
  210. if dt == np.dtype('i2'):
  211. if version < 2.:
  212. reformat_integer_v1(subdata, nbchannel, header)
  213. elif version >= 2.:
  214. reformat_integer_v2(subdata, nbchannel, header)
  215. pos += length
  216. if version < 2.:
  217. chans = [chan_num for chan_num in
  218. header['nADCSamplingSeq'] if chan_num >= 0]
  219. else:
  220. chans = range(nbchannel)
  221. for n, i in enumerate(chans[:nbchannel]): # fix SamplingSeq
  222. if version < 2.:
  223. name = header['sADCChannelName'][i].replace(b' ', b'')
  224. unit = header['sADCUnits'][i].replace(b'\xb5', b'u').\
  225. replace(b' ', b'').decode('utf-8') # \xb5 is µ
  226. num = header['nADCPtoLChannelMap'][i]
  227. elif version >= 2.:
  228. lADCIi = header['listADCInfo'][i]
  229. name = lADCIi['ADCChNames'].replace(b' ', b'')
  230. unit = lADCIi['ADCChUnits'].replace(b'\xb5', b'u').\
  231. replace(b' ', b'').decode('utf-8')
  232. num = header['listADCInfo'][i]['nADCNum']
  233. if (fSynchTimeUnit == 0):
  234. t_start = float(episode_array[j]['offset']) / sampling_rate
  235. else:
  236. t_start = float(episode_array[j]['offset']) * fSynchTimeUnit *1e-6* pq.s
  237. t_start = t_start.rescale('s')
  238. try:
  239. pq.Quantity(1, unit)
  240. except:
  241. unit = ''
  242. if lazy:
  243. signal = [] * pq.Quantity(1, unit)
  244. else:
  245. signal = pq.Quantity(subdata[:, n], unit)
  246. anaSig = AnalogSignal(signal, sampling_rate=sampling_rate,
  247. t_start=t_start,
  248. name=name.decode("utf-8"),
  249. channel_index=int(num))
  250. if lazy:
  251. anaSig.lazy_shape = length / nbchannel
  252. seg.analogsignals.append(anaSig)
  253. bl.segments.append(seg)
  254. if mode in [3, 5]: # TODO check if tags exits in other mode
  255. # tag is EventArray that should be attached to Block
  256. # It is attched to the first Segment
  257. times = []
  258. labels = []
  259. comments = []
  260. for i, tag in enumerate(header['listTag']):
  261. times.append(tag['lTagTime']/sampling_rate)
  262. labels.append(str(tag['nTagType']))
  263. comments.append(clean_string(tag['sComment']))
  264. times = np.array(times)
  265. labels = np.array(labels, dtype='S')
  266. comments = np.array(comments, dtype='S')
  267. # attach all tags to the first segment.
  268. seg = bl.segments[0]
  269. if lazy:
  270. ea = Event(times=[] * pq.s, labels=np.array([], dtype='S'))
  271. ea.lazy_shape = len(times)
  272. else:
  273. ea = Event(times=times * pq.s, labels=labels,
  274. comments=comments)
  275. seg.events.append(ea)
  276. bl.create_many_to_one_relationship()
  277. return bl
  278. def read_header(self,):
  279. """
  280. read the header of the file
  281. The strategy here differs from the original script under Matlab.
  282. In the original script for ABF2, it completes the header with
  283. information that is located in other structures.
  284. In ABF2 this function returns header with sub dict:
  285. sections (ABF2)
  286. protocol (ABF2)
  287. listTags (ABF1&2)
  288. listADCInfo (ABF2)
  289. listDACInfo (ABF2)
  290. dictEpochInfoPerDAC (ABF2)
  291. that contains more information.
  292. """
  293. fid = StructFile(open(self.filename, 'rb')) # fix for py3
  294. # version
  295. f_file_signature = fid.read(4)
  296. if f_file_signature == b'ABF ': # fix for p3 where read returns bytes
  297. header_description = headerDescriptionV1
  298. elif f_file_signature == b'ABF2':
  299. header_description = headerDescriptionV2
  300. else:
  301. return None
  302. # construct dict
  303. header = {}
  304. for key, offset, fmt in header_description:
  305. val = fid.read_f(fmt, offset=offset)
  306. if len(val) == 1:
  307. header[key] = val[0]
  308. else:
  309. header[key] = np.array(val)
  310. # correction of version number and starttime
  311. if f_file_signature == b'ABF ':
  312. header['lFileStartTime'] += header[
  313. 'nFileStartMillisecs'] * .001
  314. elif f_file_signature == b'ABF2':
  315. n = header['fFileVersionNumber']
  316. header['fFileVersionNumber'] = n[3] + 0.1 * n[2] +\
  317. 0.01 * n[1] + 0.001 * n[0]
  318. header['lFileStartTime'] = header['uFileStartTimeMS'] * .001
  319. if header['fFileVersionNumber'] < 2.:
  320. # tags
  321. listTag = []
  322. for i in range(header['lNumTagEntries']):
  323. fid.seek(header['lTagSectionPtr'] + i * 64)
  324. tag = {}
  325. for key, fmt in TagInfoDescription:
  326. val = fid.read_f(fmt)
  327. if len(val) == 1:
  328. tag[key] = val[0]
  329. else:
  330. tag[key] = np.array(val)
  331. listTag.append(tag)
  332. header['listTag'] = listTag
  333. #protocol name formatting #TODO move to read_protocol?
  334. header['sProtocolPath'] = clean_string(header['sProtocolPath'])
  335. header['sProtocolPath'] = header['sProtocolPath'].\
  336. replace(b'\\', b'/')
  337. elif header['fFileVersionNumber'] >= 2.:
  338. # in abf2 some info are in other place
  339. # sections
  340. sections = {}
  341. for s, sectionName in enumerate(sectionNames):
  342. uBlockIndex, uBytes, llNumEntries =\
  343. fid.read_f('IIl', offset=76 + s * 16)
  344. sections[sectionName] = {}
  345. sections[sectionName]['uBlockIndex'] = uBlockIndex
  346. sections[sectionName]['uBytes'] = uBytes
  347. sections[sectionName]['llNumEntries'] = llNumEntries
  348. header['sections'] = sections
  349. # strings sections
  350. # hack for reading channels names and units
  351. fid.seek(sections['StringsSection']['uBlockIndex'] * BLOCKSIZE)
  352. big_string = fid.read(sections['StringsSection']['uBytes'])
  353. goodstart=-1
  354. for key in [b'AXENGN', b'clampex', b'Clampex', b'CLAMPEX', b'axoscope']:
  355. #goodstart = big_string.lower().find(key)
  356. goodstart = big_string.find(key)
  357. if goodstart!=-1: break
  358. assert goodstart!=-1, 'This file does not contain clampex, axoscope or clampfit in the header'
  359. big_string = big_string[goodstart:]
  360. strings = big_string.split(b'\x00')
  361. # ADC sections
  362. header['listADCInfo'] = []
  363. for i in range(sections['ADCSection']['llNumEntries']):
  364. # read ADCInfo
  365. fid.seek(sections['ADCSection']['uBlockIndex'] *
  366. BLOCKSIZE + sections['ADCSection']['uBytes'] * i)
  367. ADCInfo = {}
  368. for key, fmt in ADCInfoDescription:
  369. val = fid.read_f(fmt)
  370. if len(val) == 1:
  371. ADCInfo[key] = val[0]
  372. else:
  373. ADCInfo[key] = np.array(val)
  374. ADCInfo['ADCChNames'] = strings[ADCInfo['lADCChannelNameIndex'] - 1]
  375. ADCInfo['ADCChUnits'] = strings[ADCInfo['lADCUnitsIndex'] - 1]
  376. header['listADCInfo'].append(ADCInfo)
  377. # protocol sections
  378. protocol = {}
  379. fid.seek(sections['ProtocolSection']['uBlockIndex'] * BLOCKSIZE)
  380. for key, fmt in protocolInfoDescription:
  381. val = fid.read_f(fmt)
  382. if len(val) == 1:
  383. protocol[key] = val[0]
  384. else:
  385. protocol[key] = np.array(val)
  386. header['protocol'] = protocol
  387. header['sProtocolPath'] = strings[header['uProtocolPathIndex']-1]
  388. # tags
  389. listTag = []
  390. for i in range(sections['TagSection']['llNumEntries']):
  391. fid.seek(sections['TagSection']['uBlockIndex'] *
  392. BLOCKSIZE + sections['TagSection']['uBytes'] * i)
  393. tag = {}
  394. for key, fmt in TagInfoDescription:
  395. val = fid.read_f(fmt)
  396. if len(val) == 1:
  397. tag[key] = val[0]
  398. else:
  399. tag[key] = np.array(val)
  400. listTag.append(tag)
  401. header['listTag'] = listTag
  402. # DAC sections
  403. header['listDACInfo'] = []
  404. for i in range(sections['DACSection']['llNumEntries']):
  405. # read DACInfo
  406. fid.seek(sections['DACSection']['uBlockIndex'] *
  407. BLOCKSIZE + sections['DACSection']['uBytes'] * i)
  408. DACInfo = {}
  409. for key, fmt in DACInfoDescription:
  410. val = fid.read_f(fmt)
  411. if len(val) == 1:
  412. DACInfo[key] = val[0]
  413. else:
  414. DACInfo[key] = np.array(val)
  415. DACInfo['DACChNames'] = strings[DACInfo['lDACChannelNameIndex']
  416. - 1]
  417. DACInfo['DACChUnits'] = strings[
  418. DACInfo['lDACChannelUnitsIndex'] - 1]
  419. header['listDACInfo'].append(DACInfo)
  420. # EpochPerDAC sections
  421. # header['dictEpochInfoPerDAC'] is dict of dicts:
  422. # - the first index is the DAC number
  423. # - the second index is the epoch number
  424. # It has to be done like that because data may not exist
  425. # and may not be in sorted order
  426. header['dictEpochInfoPerDAC'] = {}
  427. for i in range(sections['EpochPerDACSection']['llNumEntries']):
  428. # read DACInfo
  429. fid.seek(sections['EpochPerDACSection']['uBlockIndex'] *
  430. BLOCKSIZE +
  431. sections['EpochPerDACSection']['uBytes'] * i)
  432. EpochInfoPerDAC = {}
  433. for key, fmt in EpochInfoPerDACDescription:
  434. val = fid.read_f(fmt)
  435. if len(val) == 1:
  436. EpochInfoPerDAC[key] = val[0]
  437. else:
  438. EpochInfoPerDAC[key] = np.array(val)
  439. DACNum = EpochInfoPerDAC['nDACNum']
  440. EpochNum = EpochInfoPerDAC['nEpochNum']
  441. # Checking if the key exists, if not, the value is empty
  442. # so we have to create empty dict to populate
  443. if DACNum not in header['dictEpochInfoPerDAC']:
  444. header['dictEpochInfoPerDAC'][DACNum] = {}
  445. header['dictEpochInfoPerDAC'][DACNum][EpochNum] =\
  446. EpochInfoPerDAC
  447. fid.close()
  448. return header
  449. def read_protocol(self):
  450. """
  451. Read the protocol waveform of the file, if present;
  452. function works with ABF2 only. Protocols can be reconstructed
  453. from the ABF1 header.
  454. Returns: list of segments (one for every episode)
  455. with list of analog signls (one for every DAC).
  456. """
  457. header = self.read_header()
  458. if header['fFileVersionNumber'] < 2.:
  459. raise IOError("Protocol section is only present in ABF2 files.")
  460. nADC = header['sections']['ADCSection'][
  461. 'llNumEntries'] # Number of ADC channels
  462. nDAC = header['sections']['DACSection'][
  463. 'llNumEntries'] # Number of DAC channels
  464. nSam = int(header['protocol'][
  465. 'lNumSamplesPerEpisode'] / nADC) # Number of samples per episode
  466. nEpi = header['lActualEpisodes'] # Actual number of episodes
  467. sampling_rate = 1.e6 / header['protocol'][
  468. 'fADCSequenceInterval'] * pq.Hz
  469. # Make a list of segments with analog signals with just holding levels
  470. # List of segments relates to number of episodes, as for recorded data
  471. segments = []
  472. for epiNum in range(nEpi):
  473. seg = Segment(index=epiNum)
  474. # One analog signal for each DAC in segment (episode)
  475. for DACNum in range(nDAC):
  476. t_start = 0 * pq.s # TODO: Possibly check with episode array
  477. name = header['listDACInfo'][DACNum]['DACChNames']
  478. unit = header['listDACInfo'][DACNum]['DACChUnits'].\
  479. replace(b'\xb5', b'u').decode('utf-8') # \xb5 is µ
  480. signal = np.ones(nSam) *\
  481. header['listDACInfo'][DACNum]['fDACHoldingLevel'] *\
  482. pq.Quantity(1, unit)
  483. ana_sig = AnalogSignal(signal, sampling_rate=sampling_rate,
  484. t_start=t_start, name=name.decode("utf-8"),
  485. channel_index=DACNum)
  486. # If there are epoch infos for this DAC
  487. if DACNum in header['dictEpochInfoPerDAC']:
  488. # Save last sample index
  489. i_last = int(nSam * 15625 / 10**6)
  490. # TODO guess for first holding
  491. # Go over EpochInfoPerDAC and change the analog signal
  492. # according to the epochs
  493. epochInfo = header['dictEpochInfoPerDAC'][DACNum]
  494. for epochNum, epoch in iteritems(epochInfo):
  495. i_begin = i_last
  496. i_end = i_last + epoch['lEpochInitDuration'] +\
  497. epoch['lEpochDurationInc'] * epiNum
  498. dif = i_end-i_begin
  499. ana_sig[i_begin:i_end] = np.ones((dif, 1)) *\
  500. pq.Quantity(1, unit) * (epoch['fEpochInitLevel'] +
  501. epoch['fEpochLevelInc'] *
  502. epiNum)
  503. i_last += epoch['lEpochInitDuration']
  504. seg.analogsignals.append(ana_sig)
  505. segments.append(seg)
  506. return segments
  507. BLOCKSIZE = 512
  508. headerDescriptionV1 = [
  509. ('fFileSignature', 0, '4s'),
  510. ('fFileVersionNumber', 4, 'f'),
  511. ('nOperationMode', 8, 'h'),
  512. ('lActualAcqLength', 10, 'i'),
  513. ('nNumPointsIgnored', 14, 'h'),
  514. ('lActualEpisodes', 16, 'i'),
  515. ('lFileStartTime', 24, 'i'),
  516. ('lDataSectionPtr', 40, 'i'),
  517. ('lTagSectionPtr', 44, 'i'),
  518. ('lNumTagEntries', 48, 'i'),
  519. ('lSynchArrayPtr', 92, 'i'),
  520. ('lSynchArraySize', 96, 'i'),
  521. ('nDataFormat', 100, 'h'),
  522. ('nADCNumChannels', 120, 'h'),
  523. ('fADCSampleInterval', 122, 'f'),
  524. ('fSynchTimeUnit', 130, 'f'),
  525. ('lNumSamplesPerEpisode', 138, 'i'),
  526. ('lPreTriggerSamples', 142, 'i'),
  527. ('lEpisodesPerRun', 146, 'i'),
  528. ('fADCRange', 244, 'f'),
  529. ('lADCResolution', 252, 'i'),
  530. ('nFileStartMillisecs', 366, 'h'),
  531. ('nADCPtoLChannelMap', 378, '16h'),
  532. ('nADCSamplingSeq', 410, '16h'),
  533. ('sADCChannelName', 442, '10s'*16),
  534. ('sADCUnits', 602, '8s'*16),
  535. ('fADCProgrammableGain', 730, '16f'),
  536. ('fInstrumentScaleFactor', 922, '16f'),
  537. ('fInstrumentOffset', 986, '16f'),
  538. ('fSignalGain', 1050, '16f'),
  539. ('fSignalOffset', 1114, '16f'),
  540. ('nDigitalEnable', 1436, 'h'),
  541. ('nActiveDACChannel', 1440, 'h'),
  542. ('nDigitalHolding', 1584, 'h'),
  543. ('nDigitalInterEpisode', 1586, 'h'),
  544. ('nDigitalValue', 2588, '10h'),
  545. ('lDACFilePtr', 2048, '2i'),
  546. ('lDACFileNumEpisodes', 2056, '2i'),
  547. ('fDACCalibrationFactor', 2074, '4f'),
  548. ('fDACCalibrationOffset', 2090, '4f'),
  549. ('nWaveformEnable', 2296, '2h'),
  550. ('nWaveformSource', 2300, '2h'),
  551. ('nInterEpisodeLevel', 2304, '2h'),
  552. ('nEpochType', 2308, '20h'),
  553. ('fEpochInitLevel', 2348, '20f'),
  554. ('fEpochLevelInc', 2428, '20f'),
  555. ('lEpochInitDuration', 2508, '20i'),
  556. ('lEpochDurationInc', 2588, '20i'),
  557. ('nTelegraphEnable', 4512, '16h'),
  558. ('fTelegraphAdditGain', 4576, '16f'),
  559. ('sProtocolPath', 4898, '384s'),
  560. ]
  561. headerDescriptionV2 = [
  562. ('fFileSignature', 0, '4s'),
  563. ('fFileVersionNumber', 4, '4b'),
  564. ('uFileInfoSize', 8, 'I'),
  565. ('lActualEpisodes', 12, 'I'),
  566. ('uFileStartDate', 16, 'I'),
  567. ('uFileStartTimeMS', 20, 'I'),
  568. ('uStopwatchTime', 24, 'I'),
  569. ('nFileType', 28, 'H'),
  570. ('nDataFormat', 30, 'H'),
  571. ('nSimultaneousScan', 32, 'H'),
  572. ('nCRCEnable', 34, 'H'),
  573. ('uFileCRC', 36, 'I'),
  574. ('FileGUID', 40, 'I'),
  575. ('uCreatorVersion', 56, 'I'),
  576. ('uCreatorNameIndex', 60, 'I'),
  577. ('uModifierVersion', 64, 'I'),
  578. ('uModifierNameIndex', 68, 'I'),
  579. ('uProtocolPathIndex', 72, 'I'),
  580. ]
  581. sectionNames = [
  582. 'ProtocolSection',
  583. 'ADCSection',
  584. 'DACSection',
  585. 'EpochSection',
  586. 'ADCPerDACSection',
  587. 'EpochPerDACSection',
  588. 'UserListSection',
  589. 'StatsRegionSection',
  590. 'MathSection',
  591. 'StringsSection',
  592. 'DataSection',
  593. 'TagSection',
  594. 'ScopeSection',
  595. 'DeltaSection',
  596. 'VoiceTagSection',
  597. 'SynchArraySection',
  598. 'AnnotationSection',
  599. 'StatsSection',
  600. ]
  601. protocolInfoDescription = [
  602. ('nOperationMode', 'h'),
  603. ('fADCSequenceInterval', 'f'),
  604. ('bEnableFileCompression', 'b'),
  605. ('sUnused1', '3s'),
  606. ('uFileCompressionRatio', 'I'),
  607. ('fSynchTimeUnit', 'f'),
  608. ('fSecondsPerRun', 'f'),
  609. ('lNumSamplesPerEpisode', 'i'),
  610. ('lPreTriggerSamples', 'i'),
  611. ('lEpisodesPerRun', 'i'),
  612. ('lRunsPerTrial', 'i'),
  613. ('lNumberOfTrials', 'i'),
  614. ('nAveragingMode', 'h'),
  615. ('nUndoRunCount', 'h'),
  616. ('nFirstEpisodeInRun', 'h'),
  617. ('fTriggerThreshold', 'f'),
  618. ('nTriggerSource', 'h'),
  619. ('nTriggerAction', 'h'),
  620. ('nTriggerPolarity', 'h'),
  621. ('fScopeOutputInterval', 'f'),
  622. ('fEpisodeStartToStart', 'f'),
  623. ('fRunStartToStart', 'f'),
  624. ('lAverageCount', 'i'),
  625. ('fTrialStartToStart', 'f'),
  626. ('nAutoTriggerStrategy', 'h'),
  627. ('fFirstRunDelayS', 'f'),
  628. ('nChannelStatsStrategy', 'h'),
  629. ('lSamplesPerTrace', 'i'),
  630. ('lStartDisplayNum', 'i'),
  631. ('lFinishDisplayNum', 'i'),
  632. ('nShowPNRawData', 'h'),
  633. ('fStatisticsPeriod', 'f'),
  634. ('lStatisticsMeasurements', 'i'),
  635. ('nStatisticsSaveStrategy', 'h'),
  636. ('fADCRange', 'f'),
  637. ('fDACRange', 'f'),
  638. ('lADCResolution', 'i'),
  639. ('lDACResolution', 'i'),
  640. ('nExperimentType', 'h'),
  641. ('nManualInfoStrategy', 'h'),
  642. ('nCommentsEnable', 'h'),
  643. ('lFileCommentIndex', 'i'),
  644. ('nAutoAnalyseEnable', 'h'),
  645. ('nSignalType', 'h'),
  646. ('nDigitalEnable', 'h'),
  647. ('nActiveDACChannel', 'h'),
  648. ('nDigitalHolding', 'h'),
  649. ('nDigitalInterEpisode', 'h'),
  650. ('nDigitalDACChannel', 'h'),
  651. ('nDigitalTrainActiveLogic', 'h'),
  652. ('nStatsEnable', 'h'),
  653. ('nStatisticsClearStrategy', 'h'),
  654. ('nLevelHysteresis', 'h'),
  655. ('lTimeHysteresis', 'i'),
  656. ('nAllowExternalTags', 'h'),
  657. ('nAverageAlgorithm', 'h'),
  658. ('fAverageWeighting', 'f'),
  659. ('nUndoPromptStrategy', 'h'),
  660. ('nTrialTriggerSource', 'h'),
  661. ('nStatisticsDisplayStrategy', 'h'),
  662. ('nExternalTagType', 'h'),
  663. ('nScopeTriggerOut', 'h'),
  664. ('nLTPType', 'h'),
  665. ('nAlternateDACOutputState', 'h'),
  666. ('nAlternateDigitalOutputState', 'h'),
  667. ('fCellID', '3f'),
  668. ('nDigitizerADCs', 'h'),
  669. ('nDigitizerDACs', 'h'),
  670. ('nDigitizerTotalDigitalOuts', 'h'),
  671. ('nDigitizerSynchDigitalOuts', 'h'),
  672. ('nDigitizerType', 'h'),
  673. ]
  674. ADCInfoDescription = [
  675. ('nADCNum', 'h'),
  676. ('nTelegraphEnable', 'h'),
  677. ('nTelegraphInstrument', 'h'),
  678. ('fTelegraphAdditGain', 'f'),
  679. ('fTelegraphFilter', 'f'),
  680. ('fTelegraphMembraneCap', 'f'),
  681. ('nTelegraphMode', 'h'),
  682. ('fTelegraphAccessResistance', 'f'),
  683. ('nADCPtoLChannelMap', 'h'),
  684. ('nADCSamplingSeq', 'h'),
  685. ('fADCProgrammableGain', 'f'),
  686. ('fADCDisplayAmplification', 'f'),
  687. ('fADCDisplayOffset', 'f'),
  688. ('fInstrumentScaleFactor', 'f'),
  689. ('fInstrumentOffset', 'f'),
  690. ('fSignalGain', 'f'),
  691. ('fSignalOffset', 'f'),
  692. ('fSignalLowpassFilter', 'f'),
  693. ('fSignalHighpassFilter', 'f'),
  694. ('nLowpassFilterType', 'b'),
  695. ('nHighpassFilterType', 'b'),
  696. ('fPostProcessLowpassFilter', 'f'),
  697. ('nPostProcessLowpassFilterType', 'c'),
  698. ('bEnabledDuringPN', 'b'),
  699. ('nStatsChannelPolarity', 'h'),
  700. ('lADCChannelNameIndex', 'i'),
  701. ('lADCUnitsIndex', 'i'),
  702. ]
  703. TagInfoDescription = [
  704. ('lTagTime', 'i'),
  705. ('sComment', '56s'),
  706. ('nTagType', 'h'),
  707. ('nVoiceTagNumber_or_AnnotationIndex', 'h'),
  708. ]
  709. DACInfoDescription = [
  710. ('nDACNum', 'h'),
  711. ('nTelegraphDACScaleFactorEnable', 'h'),
  712. ('fInstrumentHoldingLevel', 'f'),
  713. ('fDACScaleFactor', 'f'),
  714. ('fDACHoldingLevel', 'f'),
  715. ('fDACCalibrationFactor', 'f'),
  716. ('fDACCalibrationOffset', 'f'),
  717. ('lDACChannelNameIndex', 'i'),
  718. ('lDACChannelUnitsIndex', 'i'),
  719. ('lDACFilePtr', 'i'),
  720. ('lDACFileNumEpisodes', 'i'),
  721. ('nWaveformEnable', 'h'),
  722. ('nWaveformSource', 'h'),
  723. ('nInterEpisodeLevel', 'h'),
  724. ('fDACFileScale', 'f'),
  725. ('fDACFileOffset', 'f'),
  726. ('lDACFileEpisodeNum', 'i'),
  727. ('nDACFileADCNum', 'h'),
  728. ('nConditEnable', 'h'),
  729. ('lConditNumPulses', 'i'),
  730. ('fBaselineDuration', 'f'),
  731. ('fBaselineLevel', 'f'),
  732. ('fStepDuration', 'f'),
  733. ('fStepLevel', 'f'),
  734. ('fPostTrainPeriod', 'f'),
  735. ('fPostTrainLevel', 'f'),
  736. ('nMembTestEnable', 'h'),
  737. ('nLeakSubtractType', 'h'),
  738. ('nPNPolarity', 'h'),
  739. ('fPNHoldingLevel', 'f'),
  740. ('nPNNumADCChannels', 'h'),
  741. ('nPNPosition', 'h'),
  742. ('nPNNumPulses', 'h'),
  743. ('fPNSettlingTime', 'f'),
  744. ('fPNInterpulse', 'f'),
  745. ('nLTPUsageOfDAC', 'h'),
  746. ('nLTPPresynapticPulses', 'h'),
  747. ('lDACFilePathIndex', 'i'),
  748. ('fMembTestPreSettlingTimeMS', 'f'),
  749. ('fMembTestPostSettlingTimeMS', 'f'),
  750. ('nLeakSubtractADCIndex', 'h'),
  751. ('sUnused', '124s'),
  752. ]
  753. EpochInfoPerDACDescription = [
  754. ('nEpochNum', 'h'),
  755. ('nDACNum', 'h'),
  756. ('nEpochType', 'h'),
  757. ('fEpochInitLevel', 'f'),
  758. ('fEpochLevelInc', 'f'),
  759. ('lEpochInitDuration', 'i'),
  760. ('lEpochDurationInc', 'i'),
  761. ('lEpochPulsePeriod', 'i'),
  762. ('lEpochPulseWidth', 'i'),
  763. ('sUnused', '18s'),
  764. ]
  765. EpochInfoDescription = [
  766. ('nEpochNum', 'h'),
  767. ('nDigitalValue', 'h'),
  768. ('nDigitalTrainValue', 'h'),
  769. ('nAlternateDigitalValue', 'h'),
  770. ('nAlternateDigitalTrainValue', 'h'),
  771. ('bEpochCompression', 'b'),
  772. ('sUnused', '21s'),
  773. ]