alphaomegaio.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. # -*- coding: utf-8 -*-
  2. """
  3. Class for reading data from Alpha Omega .map files.
  4. This class is an experimental reader with important limitations.
  5. See the source code for details of the limitations.
  6. The code of this reader is of alpha quality and received very limited testing.
  7. This code is written from the incomplete file specifications available in:
  8. [1] AlphaMap Data Acquisition System User's Manual Version 10.1.1
  9. Section 5 APPENDIX B: ALPHAMAP FILE STRUCTURE, pages 120-140
  10. Edited by ALPHA OMEGA Home Office: P.O. Box 810, Nazareth Illit 17105, Israel
  11. http://www.alphaomega-eng.com/
  12. and from the source code of a C software for conversion of .map files to
  13. .eeg elan software files :
  14. [2] alphamap2eeg 1.0, 12/03/03, Anne CHEYLUS - CNRS ISC UMR 5015
  15. Supported : Read
  16. @author : sgarcia, Florent Jaillet
  17. """
  18. # NOTE: For some specific types of comments, the following convention is used:
  19. # "TODO:" Desirable future evolution
  20. # "WARNING:" Information about code that is based on broken or missing
  21. # specifications and that might be wrong
  22. # Main limitations of this reader:
  23. # - The reader is only able to load data stored in data blocks of type 5
  24. # (data block for one channel). In particular it means that it doesn't
  25. # support signals stored in blocks of type 7 (data block for multiple
  26. # channels).
  27. # For more details on these data blocks types, see 5.4.1 and 5.4.2 p 127 in
  28. # [1].
  29. # - Rather than supporting all the neo objects types that could be extracted
  30. # from the file, all read data are returned in AnalogSignal objects, even for
  31. # digital channels or channels containing spiking informations.
  32. # - Digital channels are not converted to events or events array as they
  33. # should.
  34. # - Loading multichannel signals as AnalogSignalArrays is not supported.
  35. # - Many data or metadata that are avalaible in the file and that could be
  36. # represented in some way in the neo model are not extracted. In particular
  37. # scaling of the data and extraction of the units of the signals are not
  38. # supported.
  39. # - It received very limited testing, exlusively using python 2.6.6. In
  40. # particular it has not been tested using Python 3.x.
  41. #
  42. # These limitations are mainly due to the following reasons:
  43. # - Incomplete, unclear and in some places innacurate specifications of the
  44. # format in [1].
  45. # - Lack of test files containing all the types of data blocks of interest
  46. # (in particular no file with type 7 data block for multiple channels where
  47. # available when writing this code).
  48. # - Lack of knowledge of the Alphamap software and the associated data models.
  49. # - Lack of time (especially as the specifications are incomplete, a lot of
  50. # reverse engineering and testing is required, which makes the development of
  51. # this IO very painful and long).
  52. # needed for python 3 compatibility
  53. from __future__ import absolute_import, division
  54. # specific imports
  55. import datetime
  56. import os
  57. import struct
  58. # file no longer exists in Python3
  59. try:
  60. file
  61. except NameError:
  62. import io
  63. file = io.BufferedReader
  64. # note neo.core need only numpy and quantities
  65. import numpy as np
  66. import quantities as pq
  67. from neo.io.baseio import BaseIO
  68. from neo.core import Block, Segment, AnalogSignal
  69. class AlphaOmegaIO(BaseIO):
  70. """
  71. Class for reading data from Alpha Omega .map files (experimental)
  72. This class is an experimental reader with important limitations.
  73. See the source code for details of the limitations.
  74. The code of this reader is of alpha quality and received very limited
  75. testing.
  76. Usage:
  77. >>> from neo import io
  78. >>> r = io.AlphaOmegaIO( filename = 'File_AlphaOmega_1.map')
  79. >>> blck = r.read_block()
  80. >>> print blck.segments[0].analogsignals
  81. """
  82. is_readable = True # This is a reading only class
  83. is_writable = False # writing is not supported
  84. # This class is able to directly or indirectly read the following kind of
  85. # objects
  86. supported_objects = [Block, Segment, AnalogSignal]
  87. # TODO: Add support for other objects that should be extractable from .map
  88. # files (Event, Epoch?, Epoch Array?, SpikeTrain?)
  89. # This class can only return a Block
  90. readable_objects = [Block]
  91. # TODO : create readers for different type of objects (Segment,
  92. # AnalogSignal,...)
  93. # This class is not able to write objects
  94. writeable_objects = []
  95. # This is for GUI stuff : a definition for parameters when reading.
  96. read_params = {Block: []}
  97. # Writing is not supported, so no GUI stuff
  98. write_params = None
  99. name = 'AlphaOmega'
  100. extensions = ['map']
  101. mode = 'file'
  102. def __init__(self, filename=None):
  103. """
  104. Arguments:
  105. filename : the .map Alpha Omega file name
  106. """
  107. BaseIO.__init__(self)
  108. self.filename = filename
  109. # write is not supported so I do not overload write method from BaseIO
  110. def read_block(self, lazy=False):
  111. """
  112. Return a Block.
  113. """
  114. assert not lazy, 'Do not support lazy'
  115. def count_samples(m_length):
  116. """
  117. Count the number of signal samples available in a type 5 data block
  118. of length m_length
  119. """
  120. # for information about type 5 data block, see [1]
  121. count = int((m_length - 6) / 2 - 2)
  122. # -6 corresponds to the header of block 5, and the -2 take into
  123. # account the fact that last 2 values are not available as the 4
  124. # corresponding bytes are coding the time stamp of the beginning
  125. # of the block
  126. return count
  127. # create the neo Block that will be returned at the end
  128. blck = Block(file_origin=os.path.basename(self.filename))
  129. blck.file_origin = os.path.basename(self.filename)
  130. fid = open(self.filename, 'rb')
  131. # NOTE: in the following, the word "block" is used in the sense used in
  132. # the alpha-omega specifications (ie a data chunk in the file), rather
  133. # than in the sense of the usual Block object in neo
  134. # step 1: read the headers of all the data blocks to load the file
  135. # structure
  136. pos_block = 0 # position of the current block in the file
  137. file_blocks = [] # list of data blocks available in the file
  138. seg = Segment(file_origin=os.path.basename(self.filename))
  139. seg.file_origin = os.path.basename(self.filename)
  140. blck.segments.append(seg)
  141. while True:
  142. first_4_bytes = fid.read(4)
  143. if len(first_4_bytes) < 4:
  144. # we have reached the end of the file
  145. break
  146. else:
  147. m_length, m_TypeBlock = struct.unpack('Hcx', first_4_bytes)
  148. block = HeaderReader(fid,
  149. dict_header_type.get(m_TypeBlock,
  150. Type_Unknown)).read_f()
  151. block.update({'m_length': m_length,
  152. 'm_TypeBlock': m_TypeBlock,
  153. 'pos': pos_block})
  154. if m_TypeBlock == '2':
  155. # The beginning of the block of type '2' is identical for
  156. # all types of channels, but the following part depends on
  157. # the type of channel. So we need a special case here.
  158. # WARNING: How to check the type of channel is not
  159. # described in the documentation. So here I use what is
  160. # proposed in the C code [2].
  161. # According to this C code, it seems that the 'm_isAnalog'
  162. # is used to distinguished analog and digital channels, and
  163. # 'm_Mode' encodes the type of analog channel:
  164. # 0 for continuous, 1 for level, 2 for external trigger.
  165. # But in some files, I found channels that seemed to be
  166. # continuous channels with 'm_Modes' = 128 or 192. So I
  167. # decided to consider every channel with 'm_Modes'
  168. # different from 1 or 2 as continuous. I also couldn't
  169. # check that values of 1 and 2 are really for level and
  170. # external trigger as I had no test files containing data
  171. # of this types.
  172. type_subblock = 'unknown_channel_type(m_Mode=' \
  173. + str(block['m_Mode']) + ')'
  174. description = Type2_SubBlockUnknownChannels
  175. block.update({'m_Name': 'unknown_name'})
  176. if block['m_isAnalog'] == 0:
  177. # digital channel
  178. type_subblock = 'digital'
  179. description = Type2_SubBlockDigitalChannels
  180. elif block['m_isAnalog'] == 1:
  181. # analog channel
  182. if block['m_Mode'] == 1:
  183. # level channel
  184. type_subblock = 'level'
  185. description = Type2_SubBlockLevelChannels
  186. elif block['m_Mode'] == 2:
  187. # external trigger channel
  188. type_subblock = 'external_trigger'
  189. description = Type2_SubBlockExtTriggerChannels
  190. else:
  191. # continuous channel
  192. type_subblock = 'continuous(Mode' \
  193. + str(block['m_Mode']) + ')'
  194. description = Type2_SubBlockContinuousChannels
  195. subblock = HeaderReader(fid, description).read_f()
  196. block.update(subblock)
  197. block.update({'type_subblock': type_subblock})
  198. file_blocks.append(block)
  199. pos_block += m_length
  200. fid.seek(pos_block)
  201. # step 2: find the available channels
  202. list_chan = [] # list containing indexes of channel blocks
  203. for ind_block, block in enumerate(file_blocks):
  204. if block['m_TypeBlock'] == '2':
  205. list_chan.append(ind_block)
  206. # step 3: find blocks containing data for the available channels
  207. list_data = [] # list of lists of indexes of data blocks
  208. # corresponding to each channel
  209. for ind_chan, chan in enumerate(list_chan):
  210. list_data.append([])
  211. num_chan = file_blocks[chan]['m_numChannel']
  212. for ind_block, block in enumerate(file_blocks):
  213. if block['m_TypeBlock'] == '5':
  214. if block['m_numChannel'] == num_chan:
  215. list_data[ind_chan].append(ind_block)
  216. # step 4: compute the length (number of samples) of the channels
  217. chan_len = np.zeros(len(list_data), dtype=np.int)
  218. for ind_chan, list_blocks in enumerate(list_data):
  219. for ind_block in list_blocks:
  220. chan_len[ind_chan] += count_samples(
  221. file_blocks[ind_block]['m_length'])
  222. # step 5: find channels for which data are available
  223. ind_valid_chan = np.nonzero(chan_len)[0]
  224. # step 6: load the data
  225. # TODO give the possibility to load data as AnalogSignalArrays
  226. for ind_chan in ind_valid_chan:
  227. list_blocks = list_data[ind_chan]
  228. ind = 0 # index in the data vector
  229. # read time stamp for the beginning of the signal
  230. form = '<l' # reading format
  231. ind_block = list_blocks[0]
  232. count = count_samples(file_blocks[ind_block]['m_length'])
  233. fid.seek(file_blocks[ind_block]['pos'] + 6 + count * 2)
  234. buf = fid.read(struct.calcsize(form))
  235. val = struct.unpack(form, buf)
  236. start_index = val[0]
  237. # WARNING: in the following blocks are read supposing taht they
  238. # are all contiguous and sorted in time. I don't know if it's
  239. # always the case. Maybe we should use the time stamp of each
  240. # data block to choose where to put the read data in the array.
  241. temp_array = np.empty(chan_len[ind_chan], dtype=np.int16)
  242. # NOTE: we could directly create an empty AnalogSignal and
  243. # load the data in it, but it is much faster to load data
  244. # in a temporary numpy array and create the AnalogSignals
  245. # from this temporary array
  246. for ind_block in list_blocks:
  247. count = count_samples(
  248. file_blocks[ind_block]['m_length'])
  249. fid.seek(file_blocks[ind_block]['pos'] + 6)
  250. temp_array[ind:ind + count] = \
  251. np.fromfile(fid, dtype=np.int16, count=count)
  252. ind += count
  253. sampling_rate = \
  254. file_blocks[list_chan[ind_chan]]['m_SampleRate'] * pq.kHz
  255. t_start = (start_index / sampling_rate).simplified
  256. ana_sig = AnalogSignal(temp_array,
  257. sampling_rate=sampling_rate,
  258. t_start=t_start,
  259. name=file_blocks
  260. [list_chan[ind_chan]]['m_Name'],
  261. file_origin=os.path.basename(self.filename),
  262. units=pq.dimensionless)
  263. # todo apibreak: create ChannelIndex for each signals
  264. # ana_sig.channel_index = \
  265. # file_blocks[list_chan[ind_chan]]['m_numChannel']
  266. ana_sig.annotate(channel_name=file_blocks[list_chan[ind_chan]]['m_Name'])
  267. ana_sig.annotate(channel_type=file_blocks[list_chan[ind_chan]]['type_subblock'])
  268. seg.analogsignals.append(ana_sig)
  269. fid.close()
  270. if file_blocks[0]['m_TypeBlock'] == 'h': # this should always be true
  271. blck.rec_datetime = datetime.datetime(
  272. file_blocks[0]['m_date_year'],
  273. file_blocks[0]['m_date_month'],
  274. file_blocks[0]['m_date_day'],
  275. file_blocks[0]['m_time_hour'],
  276. file_blocks[0]['m_time_minute'],
  277. file_blocks[0]['m_time_second'],
  278. 10000 * file_blocks[0]['m_time_hsecond'])
  279. # the 10000 is here to convert m_time_hsecond from centisecond
  280. # to microsecond
  281. version = file_blocks[0]['m_version']
  282. blck.annotate(alphamap_version=version)
  283. seg.rec_datetime = blck.rec_datetime.replace()
  284. # I couldn't find a simple copy function for datetime,
  285. # using replace without arguments is a twisted way to make a
  286. # copy
  287. seg.annotate(alphamap_version=version)
  288. blck.create_many_to_one_relationship()
  289. return blck
  290. """
  291. Information for special types in [1]:
  292. _dostime_t type definition:
  293. struct dos_time_t
  294. {
  295. unsigned char hour; /* hours (0-23)*/
  296. unsigned char minute; /* minutes (0-59)*/
  297. unsigned char second; /* seconds (0-59) */
  298. unsigned char hsecond; /* seconds/ 100 (0-99)*/
  299. }
  300. _dosdate_t type definition:
  301. struct _dosdate_t
  302. {
  303. unsigned char day; /* day of month( 1-31) */
  304. unsigned char month; /* month (1-12) */
  305. unsigned int year; /* year (1980-2099) */
  306. unsigned char dayofweek; /* day of week (0 = Sunday) */
  307. }
  308. WINDOWPLACEMENT16 type definition (according to WINE source code):
  309. typedef struct
  310. {
  311. UINT16 length;
  312. UINT16 flags;
  313. UINT16 showCmd;
  314. POINT16 ptMinPosition;
  315. POINT16 ptMaxPosition;
  316. RECT16 rcNormalPosition;
  317. } WINDOWPLACEMENT16,*LPNONCLIENTMETRICS16;
  318. """
  319. max_string_len = '32s' # maximal length of variable length strings in the file
  320. # WARNING: I don't know what is the real value here. According to [1] p 139
  321. # it seems that it could be 20. Some tests would be needed to check this.
  322. # WARNING: A cleaner way to handle strings reading is suitable. Currently I
  323. # read a buffer of max_string_len bytes and look for the C "end of string"
  324. # character ('\x00'). It would be better either to read characters until
  325. # reaching '\x00' or to read the exact number of characters needed, if the
  326. # length of a string can be deduced from the lentgh of the block and the number
  327. # of bytes already read (it seems possible, at least for certain block types).
  328. # WARNING: Some test files contains data blocks of type 'b' and they are not
  329. # described in the documentation.
  330. # The name of the keys in the folowing dicts are chosen to match as closely as
  331. # possible the names in document [1]
  332. TypeH_Header = [
  333. ('m_nextBlock', 'l'),
  334. ('m_version', 'h'),
  335. ('m_time_hour', 'B'),
  336. ('m_time_minute', 'B'),
  337. ('m_time_second', 'B'),
  338. ('m_time_hsecond', 'B'),
  339. ('m_date_day', 'B'),
  340. ('m_date_month', 'B'),
  341. ('m_date_year', 'H'),
  342. ('m_date_dayofweek', 'B'),
  343. ('blank', 'x'), # one byte blank because of the 2 bytes alignement
  344. ('m_MinimumTime', 'd'),
  345. ('m_MaximumTime', 'd')]
  346. Type0_SetBoards = [
  347. ('m_nextBlock', 'l'),
  348. ('m_BoardCount', 'h'),
  349. ('m_GroupCount', 'h'),
  350. ('m_placeMainWindow', 'x')] # WARNING: unknown type ('x' is wrong)
  351. Type1_Boards = [ # WARNING: needs to be checked
  352. ('m_nextBlock', 'l'),
  353. ('m_Number', 'h'),
  354. ('m_countChannel', 'h'),
  355. ('m_countAnIn', 'h'),
  356. ('m_countAnOut', 'h'),
  357. ('m_countDigIn', 'h'),
  358. ('m_countDigOut', 'h'),
  359. ('m_TrigCount', 'h'), # not defined in 5.3.3 but appears in 5.5.1 and
  360. # seems to really exist in files
  361. # WARNING: check why 'm_TrigCount is not in the C code [2]
  362. ('m_Amplitude', 'f'),
  363. ('m_cSampleRate', 'f'), # sample rate seems to be given in kHz
  364. ('m_Duration', 'f'),
  365. ('m_nPreTrigmSec', 'f'),
  366. ('m_nPostTrigmSec', 'f'),
  367. ('m_TrgMode', 'h'),
  368. ('m_LevelValue', 'h'), # after this line, 5.3.3 is wrong,
  369. # check example in 5.5.1 for the right fields
  370. # WARNING: check why the following part is not corrected in the C code [2]
  371. ('m_nSamples', 'h'),
  372. ('m_fRMS', 'f'),
  373. ('m_ScaleFactor', 'f'),
  374. ('m_DapTime', 'f'),
  375. ('m_nameBoard', max_string_len)]
  376. # ('m_DiscMaxValue','h'), # WARNING: should this exist?
  377. # ('m_DiscMinValue','h') # WARNING: should this exist?
  378. Type2_DefBlocksChannels = [
  379. # common parameters for all types of channels
  380. ('m_nextBlock', 'l'),
  381. ('m_isAnalog', 'h'),
  382. ('m_isInput', 'h'),
  383. ('m_numChannel', 'h'),
  384. ('m_numColor', 'h'),
  385. ('m_Mode', 'h')]
  386. Type2_SubBlockContinuousChannels = [
  387. # continuous channels parameters
  388. ('blank', '2x'), # WARNING: this is not in the specs but it seems needed
  389. ('m_Amplitude', 'f'),
  390. ('m_SampleRate', 'f'),
  391. ('m_ContBlkSize', 'h'),
  392. ('m_ModeSpike', 'h'), # WARNING: the C code [2] uses usigned short here
  393. ('m_Duration', 'f'),
  394. ('m_bAutoScale', 'h'),
  395. ('m_Name', max_string_len)]
  396. Type2_SubBlockLevelChannels = [ # WARNING: untested
  397. # level channels parameters
  398. ('m_Amplitude', 'f'),
  399. ('m_SampleRate', 'f'),
  400. ('m_nSpikeCount', 'h'),
  401. ('m_ModeSpike', 'h'),
  402. ('m_nPreTrigmSec', 'f'),
  403. ('m_nPostTrigmSec', 'f'),
  404. ('m_LevelValue', 'h'),
  405. ('m_TrgMode', 'h'),
  406. ('m_YesRms', 'h'),
  407. ('m_bAutoScale', 'h'),
  408. ('m_Name', max_string_len)]
  409. Type2_SubBlockExtTriggerChannels = [ # WARNING: untested
  410. # external trigger channels parameters
  411. ('m_Amplitude', 'f'),
  412. ('m_SampleRate', 'f'),
  413. ('m_nSpikeCount', 'h'),
  414. ('m_ModeSpike', 'h'),
  415. ('m_nPreTrigmSec', 'f'),
  416. ('m_nPostTrigmSec', 'f'),
  417. ('m_TriggerNumber', 'h'),
  418. ('m_Name', max_string_len)]
  419. Type2_SubBlockDigitalChannels = [
  420. # digital channels parameters
  421. ('m_SampleRate', 'f'),
  422. ('m_SaveTrigger', 'h'),
  423. ('m_Duration', 'f'),
  424. ('m_PreviousStatus', 'h'), # WARNING: check difference with C code here
  425. ('m_Name', max_string_len)]
  426. Type2_SubBlockUnknownChannels = [
  427. # WARNING: We have a mode that doesn't appear in our spec, so we don't
  428. # know what are the fields.
  429. # It seems that for non-digital channels the beginning is
  430. # similar to continuous channels. Let's hope we're right...
  431. ('blank', '2x'),
  432. ('m_Amplitude', 'f'),
  433. ('m_SampleRate', 'f')]
  434. # there are probably other fields after...
  435. Type6_DefBlockTrigger = [ # WARNING: untested
  436. ('m_nextBlock', 'l'),
  437. ('m_Number', 'h'),
  438. ('m_countChannel', 'h'),
  439. ('m_StateChannels', 'i'),
  440. ('m_numChannel1', 'h'),
  441. ('m_numChannel2', 'h'),
  442. ('m_numChannel3', 'h'),
  443. ('m_numChannel4', 'h'),
  444. ('m_numChannel5', 'h'),
  445. ('m_numChannel6', 'h'),
  446. ('m_numChannel7', 'h'),
  447. ('m_numChannel8', 'h'),
  448. ('m_Name', 'c')]
  449. Type3_DefBlockGroup = [ # WARNING: untested
  450. ('m_nextBlock', 'l'),
  451. ('m_Number', 'h'),
  452. ('m_Z_Order', 'h'),
  453. ('m_countSubGroups', 'h'),
  454. ('m_placeGroupWindow', 'x'), # WARNING: unknown type ('x' is wrong)
  455. ('m_NetLoc', 'h'),
  456. ('m_locatMax', 'x'), # WARNING: unknown type ('x' is wrong)
  457. ('m_nameGroup', 'c')]
  458. Type4_DefBlockSubgroup = [ # WARNING: untested
  459. ('m_nextBlock', 'l'),
  460. ('m_Number', 'h'),
  461. ('m_TypeOverlap', 'h'),
  462. ('m_Z_Order', 'h'),
  463. ('m_countChannel', 'h'),
  464. ('m_NetLoc', 'h'),
  465. ('m_location', 'x'), # WARNING: unknown type ('x' is wrong)
  466. ('m_bIsMaximized', 'h'),
  467. ('m_numChannel1', 'h'),
  468. ('m_numChannel2', 'h'),
  469. ('m_numChannel3', 'h'),
  470. ('m_numChannel4', 'h'),
  471. ('m_numChannel5', 'h'),
  472. ('m_numChannel6', 'h'),
  473. ('m_numChannel7', 'h'),
  474. ('m_numChannel8', 'h'),
  475. ('m_Name', 'c')]
  476. Type5_DataBlockOneChannel = [
  477. ('m_numChannel', 'h')]
  478. # WARNING: 'm_numChannel' (called 'm_Number' in 5.4.1 of [1]) is supposed
  479. # to be uint according to 5.4.1 but it seems to be a short in the files
  480. # (or should it be ushort ?)
  481. # WARNING: In 5.1.1 page 121 of [1], they say "Note: 5 is used for demo
  482. # purposes, 7 is used for real data", but looking at some real datafiles,
  483. # it seems that block of type 5 are also used for real data...
  484. Type7_DataBlockMultipleChannels = [ # WARNING: unfinished
  485. ('m_lenHead', 'h'), # WARNING: unknown true type
  486. ('FINT', 'h')]
  487. # WARNING: there should be data after...
  488. TypeP_DefBlockPeriStimHist = [ # WARNING: untested
  489. ('m_Number_Chan', 'h'),
  490. ('m_Position', 'x'), # WARNING: unknown type ('x' is wrong)
  491. ('m_isStatVisible', 'h'),
  492. ('m_DurationSec', 'f'),
  493. ('m_Rows', 'i'),
  494. ('m_DurationSecPre', 'f'),
  495. ('m_Bins', 'i'),
  496. ('m_NoTrigger', 'h')]
  497. TypeF_DefBlockFRTachogram = [ # WARNING: untested
  498. ('m_Number_Chan', 'h'),
  499. ('m_Position', 'x'), # WARNING: unknown type ('x' is wrong)
  500. ('m_isStatVisible', 'h'),
  501. ('m_DurationSec', 'f'),
  502. ('m_AutoManualScale', 'i'),
  503. ('m_Max', 'i')]
  504. TypeR_DefBlockRaster = [ # WARNING: untested
  505. ('m_Number_Chan', 'h'),
  506. ('m_Position', 'x'), # WARNING: unknown type ('x' is wrong)
  507. ('m_isStatVisible', 'h'),
  508. ('m_DurationSec', 'f'),
  509. ('m_Rows', 'i'),
  510. ('m_NoTrigger', 'h')]
  511. TypeI_DefBlockISIHist = [ # WARNING: untested
  512. ('m_Number_Chan', 'h'),
  513. ('m_Position', 'x'), # WARNING: unknown type ('x' is wrong)
  514. ('m_isStatVisible', 'h'),
  515. ('m_DurationSec', 'f'),
  516. ('m_Bins', 'i'),
  517. ('m_TypeScale', 'i')]
  518. Type8_MarkerBlock = [ # WARNING: untested
  519. ('m_Number_Channel', 'h'),
  520. ('m_Time', 'l')] # WARNING: check what's the right type here.
  521. # It seems that the size of time_t type depends on the system typedef,
  522. # I put long here but I couldn't check if it is the right type
  523. Type9_ScaleBlock = [ # WARNING: untested
  524. ('m_Number_Channel', 'h'),
  525. ('m_Scale', 'f')]
  526. Type_Unknown = []
  527. dict_header_type = {
  528. 'h': TypeH_Header,
  529. '0': Type0_SetBoards,
  530. '1': Type1_Boards,
  531. '2': Type2_DefBlocksChannels,
  532. '6': Type6_DefBlockTrigger,
  533. '3': Type3_DefBlockGroup,
  534. '4': Type4_DefBlockSubgroup,
  535. '5': Type5_DataBlockOneChannel,
  536. '7': Type7_DataBlockMultipleChannels,
  537. 'P': TypeP_DefBlockPeriStimHist,
  538. 'F': TypeF_DefBlockFRTachogram,
  539. 'R': TypeR_DefBlockRaster,
  540. 'I': TypeI_DefBlockISIHist,
  541. '8': Type8_MarkerBlock,
  542. '9': Type9_ScaleBlock
  543. }
  544. class HeaderReader():
  545. def __init__(self, fid, description):
  546. self.fid = fid
  547. self.description = description
  548. def read_f(self, offset=None):
  549. if offset is not None:
  550. self.fid.seek(offset)
  551. d = {}
  552. for key, fmt in self.description:
  553. fmt = '<' + fmt # insures use of standard sizes
  554. buf = self.fid.read(struct.calcsize(fmt))
  555. if len(buf) != struct.calcsize(fmt):
  556. return None
  557. val = list(struct.unpack(fmt, buf))
  558. for i, ival in enumerate(val):
  559. if hasattr(ival, 'split'):
  560. val[i] = ival.split('\x00', 1)[0]
  561. if len(val) == 1:
  562. val = val[0]
  563. d[key] = val
  564. return d