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. class StructFile(BufferedReader):
  36. def read_f(self, fmt, offset=None):
  37. if offset is not None:
  38. self.seek(offset)
  39. return struct.unpack(fmt, self.read(struct.calcsize(fmt)))
  40. def write_f(self, fmt, offset=None, *args):
  41. if offset is not None:
  42. self.seek(offset)
  43. self.write(struct.pack(fmt, *args))
  44. def reformat_integer_v1(data, nbchannel, header):
  45. """
  46. reformat when dtype is int16 for ABF version 1
  47. """
  48. chans = [chan_num for chan_num in
  49. header['nADCSamplingSeq'] if chan_num >= 0]
  50. for n, i in enumerate(chans[:nbchannel]): # respect SamplingSeq
  51. data[:, n] /= header['fInstrumentScaleFactor'][i]
  52. data[:, n] /= header['fSignalGain'][i]
  53. data[:, n] /= header['fADCProgrammableGain'][i]
  54. if header['nTelegraphEnable'][i]:
  55. data[:, n] /= header['fTelegraphAdditGain'][i]
  56. data[:, n] *= header['fADCRange']
  57. data[:, n] /= header['lADCResolution']
  58. data[:, n] += header['fInstrumentOffset'][i]
  59. data[:, n] -= header['fSignalOffset'][i]
  60. def reformat_integer_v2(data, nbchannel, header):
  61. """
  62. reformat when dtype is int16 for ABF version 2
  63. """
  64. for i in range(nbchannel):
  65. data[:, i] /= header['listADCInfo'][i]['fInstrumentScaleFactor']
  66. data[:, i] /= header['listADCInfo'][i]['fSignalGain']
  67. data[:, i] /= header['listADCInfo'][i]['fADCProgrammableGain']
  68. if header['listADCInfo'][i]['nTelegraphEnable']:
  69. data[:, i] /= header['listADCInfo'][i]['fTelegraphAdditGain']
  70. data[:, i] *= header['protocol']['fADCRange']
  71. data[:, i] /= header['protocol']['lADCResolution']
  72. data[:, i] += header['listADCInfo'][i]['fInstrumentOffset']
  73. data[:, i] -= header['listADCInfo'][i]['fSignalOffset']
  74. def clean_string(s):
  75. s = s.rstrip(b'\x00')
  76. s = s.rstrip(b' ')
  77. return s
  78. class AxonIO(BaseIO):
  79. """
  80. Class for reading data from pCLAMP and AxoScope
  81. files (.abf version 1 and 2), developed by Molecular Device/Axon Technologies.
  82. Usage:
  83. >>> from neo import io
  84. >>> r = io.AxonIO(filename='File_axon_1.abf')
  85. >>> bl = r.read_block(lazy=False, cascade=True)
  86. >>> print bl.segments
  87. [<neo.core.segment.Segment object at 0x105516fd0>]
  88. >>> print bl.segments[0].analogsignals
  89. [<AnalogSignal(array([ 2.18811035, 2.19726562, 2.21252441, ...,
  90. 1.33056641, 1.3458252 , 1.3671875 ], dtype=float32) * pA,
  91. [0.0 s, 191.2832 s], sampling rate: 10000.0 Hz)>]
  92. >>> print bl.segments[0].events
  93. []
  94. """
  95. is_readable = True
  96. is_writable = False
  97. supported_objects = [Block, Segment, AnalogSignal, Event]
  98. readable_objects = [Block]
  99. writeable_objects = []
  100. has_header = False
  101. is_streameable = False
  102. read_params = {Block: []}
  103. write_params = None
  104. name = 'Axon'
  105. extensions = ['abf']
  106. mode = 'file'
  107. def __init__(self, filename=None):
  108. """
  109. This class read a abf file.
  110. Arguments:
  111. filename : the filename to read
  112. """
  113. BaseIO.__init__(self)
  114. self.filename = filename
  115. def read_block(self, lazy=False, cascade=True):
  116. header = self.read_header()
  117. version = header['fFileVersionNumber']
  118. bl = Block()
  119. bl.file_origin = os.path.basename(self.filename)
  120. bl.annotate(abf_version=str(version))
  121. # date and time
  122. if version < 2.:
  123. YY = 1900
  124. MM = 1
  125. DD = 1
  126. hh = int(header['lFileStartTime'] / 3600.)
  127. mm = int((header['lFileStartTime'] - hh * 3600) / 60)
  128. ss = header['lFileStartTime'] - hh * 3600 - mm * 60
  129. ms = int(np.mod(ss, 1) * 1e6)
  130. ss = int(ss)
  131. elif version >= 2.:
  132. YY = int(header['uFileStartDate'] / 10000)
  133. MM = int((header['uFileStartDate'] - YY * 10000) / 100)
  134. DD = int(header['uFileStartDate'] - YY * 10000 - MM * 100)
  135. hh = int(header['uFileStartTimeMS'] / 1000. / 3600.)
  136. mm = int((header['uFileStartTimeMS'] / 1000. - hh * 3600) / 60)
  137. ss = header['uFileStartTimeMS'] / 1000. - hh * 3600 - mm * 60
  138. ms = int(np.mod(ss, 1) * 1e6)
  139. ss = int(ss)
  140. bl.rec_datetime = datetime.datetime(YY, MM, DD, hh, mm, ss, ms)
  141. if not cascade:
  142. return bl
  143. # file format
  144. if header['nDataFormat'] == 0:
  145. dt = np.dtype('i2')
  146. elif header['nDataFormat'] == 1:
  147. dt = np.dtype('f4')
  148. if version < 2.:
  149. nbchannel = header['nADCNumChannels']
  150. head_offset = header['lDataSectionPtr'] * BLOCKSIZE + header[
  151. 'nNumPointsIgnored'] * dt.itemsize
  152. totalsize = header['lActualAcqLength']
  153. elif version >= 2.:
  154. nbchannel = header['sections']['ADCSection']['llNumEntries']
  155. head_offset = header['sections']['DataSection'][
  156. 'uBlockIndex'] * BLOCKSIZE
  157. totalsize = header['sections']['DataSection']['llNumEntries']
  158. data = np.memmap(self.filename, dt, 'r',
  159. shape=(totalsize,), offset=head_offset)
  160. # 3 possible modes
  161. if version < 2.:
  162. mode = header['nOperationMode']
  163. elif version >= 2.:
  164. mode = header['protocol']['nOperationMode']
  165. if (mode == 1) or (mode == 2) or (mode == 5) or (mode == 3):
  166. # event-driven variable-length mode (mode 1)
  167. # event-driven fixed-length mode (mode 2 or 5)
  168. # gap free mode (mode 3) can be in several episodes
  169. # read sweep pos
  170. if version < 2.:
  171. nbepisod = header['lSynchArraySize']
  172. offset_episode = header['lSynchArrayPtr'] * BLOCKSIZE
  173. elif version >= 2.:
  174. nbepisod = header['sections']['SynchArraySection'][
  175. 'llNumEntries']
  176. offset_episode = header['sections']['SynchArraySection'][
  177. 'uBlockIndex'] * BLOCKSIZE
  178. if nbepisod > 0:
  179. episode_array = np.memmap(
  180. self.filename, [('offset', 'i4'), ('len', 'i4')], 'r',
  181. shape=nbepisod, offset=offset_episode)
  182. else:
  183. episode_array = np.empty(1, [('offset', 'i4'), ('len', 'i4')])
  184. episode_array[0]['len'] = data.size
  185. episode_array[0]['offset'] = 0
  186. # sampling_rate
  187. if version < 2.:
  188. sampling_rate = 1. / (header['fADCSampleInterval'] *
  189. nbchannel * 1.e-6) * pq.Hz
  190. elif version >= 2.:
  191. sampling_rate = 1.e6 / \
  192. header['protocol']['fADCSequenceInterval'] * pq.Hz
  193. # construct block
  194. # one sweep = one segment in a block
  195. pos = 0
  196. for j in range(episode_array.size):
  197. seg = Segment(index=j)
  198. length = episode_array[j]['len']
  199. if version < 2.:
  200. fSynchTimeUnit = header['fSynchTimeUnit']
  201. elif version >= 2.:
  202. fSynchTimeUnit = header['protocol']['fSynchTimeUnit']
  203. if (fSynchTimeUnit != 0) and (mode == 1):
  204. length /= fSynchTimeUnit
  205. if not lazy:
  206. subdata = data[pos:pos+length]
  207. subdata = subdata.reshape((int(subdata.size/nbchannel),
  208. nbchannel)).astype('f')
  209. if dt == np.dtype('i2'):
  210. if version < 2.:
  211. reformat_integer_v1(subdata, nbchannel, header)
  212. elif version >= 2.:
  213. reformat_integer_v2(subdata, nbchannel, header)
  214. pos += length
  215. if version < 2.:
  216. chans = [chan_num for chan_num in
  217. header['nADCSamplingSeq'] if chan_num >= 0]
  218. else:
  219. chans = range(nbchannel)
  220. for n, i in enumerate(chans[:nbchannel]): # fix SamplingSeq
  221. if version < 2.:
  222. name = header['sADCChannelName'][i].replace(b' ', b'')
  223. unit = header['sADCUnits'][i].replace(b'\xb5', b'u').\
  224. replace(b' ', b'').decode('utf-8') # \xb5 is µ
  225. num = header['nADCPtoLChannelMap'][i]
  226. elif version >= 2.:
  227. lADCIi = header['listADCInfo'][i]
  228. name = lADCIi['ADCChNames'].replace(b' ', b'')
  229. unit = lADCIi['ADCChUnits'].replace(b'\xb5', b'u').\
  230. replace(b' ', b'').decode('utf-8')
  231. num = header['listADCInfo'][i]['nADCNum']
  232. if (fSynchTimeUnit == 0):
  233. t_start = float(episode_array[j]['offset']) / sampling_rate
  234. else:
  235. t_start = float(episode_array[j]['offset']) * fSynchTimeUnit *1e-6* pq.s
  236. t_start = t_start.rescale('s')
  237. try:
  238. pq.Quantity(1, unit)
  239. except:
  240. unit = ''
  241. if lazy:
  242. signal = [] * pq.Quantity(1, unit)
  243. else:
  244. signal = pq.Quantity(subdata[:, n], unit)
  245. anaSig = AnalogSignal(signal, sampling_rate=sampling_rate,
  246. t_start=t_start,
  247. name=str(name.decode("utf-8")),
  248. channel_index=int(num))
  249. if lazy:
  250. anaSig.lazy_shape = length / nbchannel
  251. seg.analogsignals.append(anaSig)
  252. bl.segments.append(seg)
  253. if mode in [3, 5]: # TODO check if tags exits in other mode
  254. # tag is EventArray that should be attached to Block
  255. # It is attched to the first Segment
  256. times = []
  257. labels = []
  258. comments = []
  259. for i, tag in enumerate(header['listTag']):
  260. times.append(tag['lTagTime']/sampling_rate)
  261. labels.append(str(tag['nTagType']))
  262. comments.append(clean_string(tag['sComment']))
  263. times = np.array(times)
  264. labels = np.array(labels, dtype='S')
  265. comments = np.array(comments, dtype='S')
  266. # attach all tags to the first segment.
  267. seg = bl.segments[0]
  268. if lazy:
  269. ea = Event(times=[] * pq.s, labels=np.array([], dtype='S'))
  270. ea.lazy_shape = len(times)
  271. else:
  272. ea = Event(times=times * pq.s, labels=labels,
  273. comments=comments)
  274. seg.events.append(ea)
  275. bl.create_many_to_one_relationship()
  276. return bl
  277. def read_header(self,):
  278. """
  279. read the header of the file
  280. The strategy here differs from the original script under Matlab.
  281. In the original script for ABF2, it completes the header with
  282. information that is located in other structures.
  283. In ABF2 this function returns header with sub dict:
  284. sections (ABF2)
  285. protocol (ABF2)
  286. listTags (ABF1&2)
  287. listADCInfo (ABF2)
  288. listDACInfo (ABF2)
  289. dictEpochInfoPerDAC (ABF2)
  290. that contains more information.
  291. """
  292. fid = StructFile(open(self.filename, 'rb')) # fix for py3
  293. # version
  294. f_file_signature = fid.read(4)
  295. if f_file_signature == b'ABF ': # fix for p3 where read returns bytes
  296. header_description = headerDescriptionV1
  297. elif f_file_signature == b'ABF2':
  298. header_description = headerDescriptionV2
  299. else:
  300. return None
  301. # construct dict
  302. header = {}
  303. for key, offset, fmt in header_description:
  304. val = fid.read_f(fmt, offset=offset)
  305. if len(val) == 1:
  306. header[key] = val[0]
  307. else:
  308. header[key] = np.array(val)
  309. # correction of version number and starttime
  310. if f_file_signature == b'ABF ':
  311. header['lFileStartTime'] += header[
  312. 'nFileStartMillisecs'] * .001
  313. elif f_file_signature == b'ABF2':
  314. n = header['fFileVersionNumber']
  315. header['fFileVersionNumber'] = n[3] + 0.1 * n[2] +\
  316. 0.01 * n[1] + 0.001 * n[0]
  317. header['lFileStartTime'] = header['uFileStartTimeMS'] * .001
  318. if header['fFileVersionNumber'] < 2.:
  319. # tags
  320. listTag = []
  321. for i in range(header['lNumTagEntries']):
  322. fid.seek(header['lTagSectionPtr'] + i * 64)
  323. tag = {}
  324. for key, fmt in TagInfoDescription:
  325. val = fid.read_f(fmt)
  326. if len(val) == 1:
  327. tag[key] = val[0]
  328. else:
  329. tag[key] = np.array(val)
  330. listTag.append(tag)
  331. header['listTag'] = listTag
  332. #protocol name formatting #TODO move to read_protocol?
  333. header['sProtocolPath'] = clean_string(header['sProtocolPath'])
  334. header['sProtocolPath'] = header['sProtocolPath'].\
  335. replace(b'\\', b'/')
  336. elif header['fFileVersionNumber'] >= 2.:
  337. # in abf2 some info are in other place
  338. # sections
  339. sections = {}
  340. for s, sectionName in enumerate(sectionNames):
  341. uBlockIndex, uBytes, llNumEntries =\
  342. fid.read_f('IIl', offset=76 + s * 16)
  343. sections[sectionName] = {}
  344. sections[sectionName]['uBlockIndex'] = uBlockIndex
  345. sections[sectionName]['uBytes'] = uBytes
  346. sections[sectionName]['llNumEntries'] = llNumEntries
  347. header['sections'] = sections
  348. # strings sections
  349. # hack for reading channels names and units
  350. fid.seek(sections['StringsSection']['uBlockIndex'] * BLOCKSIZE)
  351. big_string = fid.read(sections['StringsSection']['uBytes'])
  352. goodstart=-1
  353. for key in [b'AXENGN', b'clampex', b'Clampex', b'CLAMPEX', b'axoscope']:
  354. #goodstart = big_string.lower().find(key)
  355. goodstart = big_string.find(key)
  356. if goodstart!=-1: break
  357. assert goodstart!=-1, 'This file does not contain clampex, axoscope or clampfit in the header'
  358. big_string = big_string[goodstart:]
  359. strings = big_string.split(b'\x00')
  360. # ADC sections
  361. header['listADCInfo'] = []
  362. for i in range(sections['ADCSection']['llNumEntries']):
  363. # read ADCInfo
  364. fid.seek(sections['ADCSection']['uBlockIndex'] *
  365. BLOCKSIZE + sections['ADCSection']['uBytes'] * i)
  366. ADCInfo = {}
  367. for key, fmt in ADCInfoDescription:
  368. val = fid.read_f(fmt)
  369. if len(val) == 1:
  370. ADCInfo[key] = val[0]
  371. else:
  372. ADCInfo[key] = np.array(val)
  373. ADCInfo['ADCChNames'] = strings[ADCInfo['lADCChannelNameIndex'] - 1]
  374. ADCInfo['ADCChUnits'] = strings[ADCInfo['lADCUnitsIndex'] - 1]
  375. header['listADCInfo'].append(ADCInfo)
  376. # protocol sections
  377. protocol = {}
  378. fid.seek(sections['ProtocolSection']['uBlockIndex'] * BLOCKSIZE)
  379. for key, fmt in protocolInfoDescription:
  380. val = fid.read_f(fmt)
  381. if len(val) == 1:
  382. protocol[key] = val[0]
  383. else:
  384. protocol[key] = np.array(val)
  385. header['protocol'] = protocol
  386. header['sProtocolPath'] = strings[header['uProtocolPathIndex']-1]
  387. # tags
  388. listTag = []
  389. for i in range(sections['TagSection']['llNumEntries']):
  390. fid.seek(sections['TagSection']['uBlockIndex'] *
  391. BLOCKSIZE + sections['TagSection']['uBytes'] * i)
  392. tag = {}
  393. for key, fmt in TagInfoDescription:
  394. val = fid.read_f(fmt)
  395. if len(val) == 1:
  396. tag[key] = val[0]
  397. else:
  398. tag[key] = np.array(val)
  399. listTag.append(tag)
  400. header['listTag'] = listTag
  401. # DAC sections
  402. header['listDACInfo'] = []
  403. for i in range(sections['DACSection']['llNumEntries']):
  404. # read DACInfo
  405. fid.seek(sections['DACSection']['uBlockIndex'] *
  406. BLOCKSIZE + sections['DACSection']['uBytes'] * i)
  407. DACInfo = {}
  408. for key, fmt in DACInfoDescription:
  409. val = fid.read_f(fmt)
  410. if len(val) == 1:
  411. DACInfo[key] = val[0]
  412. else:
  413. DACInfo[key] = np.array(val)
  414. DACInfo['DACChNames'] = strings[DACInfo['lDACChannelNameIndex']
  415. - 1]
  416. DACInfo['DACChUnits'] = strings[
  417. DACInfo['lDACChannelUnitsIndex'] - 1]
  418. header['listDACInfo'].append(DACInfo)
  419. # EpochPerDAC sections
  420. # header['dictEpochInfoPerDAC'] is dict of dicts:
  421. # - the first index is the DAC number
  422. # - the second index is the epoch number
  423. # It has to be done like that because data may not exist
  424. # and may not be in sorted order
  425. header['dictEpochInfoPerDAC'] = {}
  426. for i in range(sections['EpochPerDACSection']['llNumEntries']):
  427. # read DACInfo
  428. fid.seek(sections['EpochPerDACSection']['uBlockIndex'] *
  429. BLOCKSIZE +
  430. sections['EpochPerDACSection']['uBytes'] * i)
  431. EpochInfoPerDAC = {}
  432. for key, fmt in EpochInfoPerDACDescription:
  433. val = fid.read_f(fmt)
  434. if len(val) == 1:
  435. EpochInfoPerDAC[key] = val[0]
  436. else:
  437. EpochInfoPerDAC[key] = np.array(val)
  438. DACNum = EpochInfoPerDAC['nDACNum']
  439. EpochNum = EpochInfoPerDAC['nEpochNum']
  440. # Checking if the key exists, if not, the value is empty
  441. # so we have to create empty dict to populate
  442. if DACNum not in header['dictEpochInfoPerDAC']:
  443. header['dictEpochInfoPerDAC'][DACNum] = {}
  444. header['dictEpochInfoPerDAC'][DACNum][EpochNum] =\
  445. EpochInfoPerDAC
  446. fid.close()
  447. return header
  448. def read_protocol(self):
  449. """
  450. Read the protocol waveform of the file, if present;
  451. function works with ABF2 only. Protocols can be reconstructed
  452. from the ABF1 header.
  453. Returns: list of segments (one for every episode)
  454. with list of analog signls (one for every DAC).
  455. """
  456. header = self.read_header()
  457. if header['fFileVersionNumber'] < 2.:
  458. raise IOError("Protocol section is only present in ABF2 files.")
  459. nADC = header['sections']['ADCSection'][
  460. 'llNumEntries'] # Number of ADC channels
  461. nDAC = header['sections']['DACSection'][
  462. 'llNumEntries'] # Number of DAC channels
  463. nSam = int(header['protocol'][
  464. 'lNumSamplesPerEpisode'] / nADC) # Number of samples per episode
  465. nEpi = header['lActualEpisodes'] # Actual number of episodes
  466. sampling_rate = 1.e6 / header['protocol'][
  467. 'fADCSequenceInterval'] * pq.Hz
  468. # Make a list of segments with analog signals with just holding levels
  469. # List of segments relates to number of episodes, as for recorded data
  470. segments = []
  471. for epiNum in range(nEpi):
  472. seg = Segment(index=epiNum)
  473. # One analog signal for each DAC in segment (episode)
  474. for DACNum in range(nDAC):
  475. t_start = 0 * pq.s # TODO: Possibly check with episode array
  476. name = header['listDACInfo'][DACNum]['DACChNames']
  477. unit = header['listDACInfo'][DACNum]['DACChUnits'].\
  478. replace(b'\xb5', b'u').decode('utf-8') # \xb5 is µ
  479. signal = np.ones(nSam) *\
  480. header['listDACInfo'][DACNum]['fDACHoldingLevel'] *\
  481. pq.Quantity(1, unit)
  482. ana_sig = AnalogSignal(signal, sampling_rate=sampling_rate,
  483. t_start=t_start, name=name.decode("utf-8"),
  484. channel_index=DACNum)
  485. # If there are epoch infos for this DAC
  486. if DACNum in header['dictEpochInfoPerDAC']:
  487. # Save last sample index
  488. i_last = int(nSam * 15625 / 10**6)
  489. # TODO guess for first holding
  490. # Go over EpochInfoPerDAC and change the analog signal
  491. # according to the epochs
  492. epochInfo = header['dictEpochInfoPerDAC'][DACNum]
  493. for epochNum, epoch in epochInfo.items():
  494. i_begin = i_last
  495. i_end = i_last + epoch['lEpochInitDuration'] +\
  496. epoch['lEpochDurationInc'] * epiNum
  497. dif = i_end-i_begin
  498. ana_sig[i_begin:i_end] = np.ones((dif, 1)) *\
  499. pq.Quantity(1, unit) * (epoch['fEpochInitLevel'] +
  500. epoch['fEpochLevelInc'] *
  501. epiNum)
  502. i_last += epoch['lEpochInitDuration'] +\
  503. epoch['lEpochDurationInc'] * epiNum
  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. ]