axonrawio.py 33 KB

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