alphaomegaio.py 24 KB

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