Scheduled service maintenance on November 22


On Friday, November 22, 2024, between 06:00 CET and 18:00 CET, GIN services will undergo planned maintenance. Extended service interruptions should be expected. We will try to keep downtimes to a minimum, but recommend that users avoid critical tasks, large data uploads, or DOI requests during this time.

We apologize for any inconvenience.

alphaomegaio.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  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(lazy = False, cascade = True)
  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,
  111. # the 2 first keyword arguments are imposed by neo.io API
  112. lazy = False,
  113. cascade = True):
  114. """
  115. Return a Block.
  116. """
  117. def count_samples(m_length):
  118. """
  119. Count the number of signal samples available in a type 5 data block
  120. of length m_length
  121. """
  122. # for information about type 5 data block, see [1]
  123. count = int((m_length-6)/2-2)
  124. # -6 corresponds to the header of block 5, and the -2 take into
  125. # account the fact that last 2 values are not available as the 4
  126. # corresponding bytes are coding the time stamp of the beginning
  127. # of the block
  128. return count
  129. # create the neo Block that will be returned at the end
  130. blck = Block(file_origin = os.path.basename(self.filename))
  131. blck.file_origin = os.path.basename(self.filename)
  132. fid = open(self.filename, 'rb')
  133. # NOTE: in the following, the word "block" is used in the sense used in
  134. # the alpha-omega specifications (ie a data chunk in the file), rather
  135. # than in the sense of the usual Block object in neo
  136. # step 1: read the headers of all the data blocks to load the file
  137. # structure
  138. pos_block = 0 # position of the current block in the file
  139. file_blocks = [] # list of data blocks available in the file
  140. if not cascade:
  141. # we read only the main header
  142. m_length, m_TypeBlock = struct.unpack('Hcx' , fid.read(4))
  143. # m_TypeBlock should be 'h', as we read the first block
  144. block = HeaderReader(fid,
  145. dict_header_type.get(m_TypeBlock,
  146. Type_Unknown)).read_f()
  147. block.update({'m_length': m_length,
  148. 'm_TypeBlock': m_TypeBlock,
  149. 'pos': pos_block})
  150. file_blocks.append(block)
  151. else: # cascade == True
  152. seg = Segment(file_origin = os.path.basename(self.filename))
  153. seg.file_origin = os.path.basename(self.filename)
  154. blck.segments.append(seg)
  155. while True:
  156. first_4_bytes = fid.read(4)
  157. if len(first_4_bytes) < 4:
  158. # we have reached the end of the file
  159. break
  160. else:
  161. m_length, m_TypeBlock = struct.unpack('Hcx', first_4_bytes)
  162. block = HeaderReader(fid,
  163. dict_header_type.get(m_TypeBlock,
  164. Type_Unknown)).read_f()
  165. block.update({'m_length': m_length,
  166. 'm_TypeBlock': m_TypeBlock,
  167. 'pos': pos_block})
  168. if m_TypeBlock == '2':
  169. # The beginning of the block of type '2' is identical for
  170. # all types of channels, but the following part depends on
  171. # the type of channel. So we need a special case here.
  172. # WARNING: How to check the type of channel is not
  173. # described in the documentation. So here I use what is
  174. # proposed in the C code [2].
  175. # According to this C code, it seems that the 'm_isAnalog'
  176. # is used to distinguished analog and digital channels, and
  177. # 'm_Mode' encodes the type of analog channel:
  178. # 0 for continuous, 1 for level, 2 for external trigger.
  179. # But in some files, I found channels that seemed to be
  180. # continuous channels with 'm_Modes' = 128 or 192. So I
  181. # decided to consider every channel with 'm_Modes'
  182. # different from 1 or 2 as continuous. I also couldn't
  183. # check that values of 1 and 2 are really for level and
  184. # external trigger as I had no test files containing data
  185. # of this types.
  186. type_subblock = 'unknown_channel_type(m_Mode=' \
  187. + str(block['m_Mode'])+ ')'
  188. description = Type2_SubBlockUnknownChannels
  189. block.update({'m_Name': 'unknown_name'})
  190. if block['m_isAnalog'] == 0:
  191. # digital channel
  192. type_subblock = 'digital'
  193. description = Type2_SubBlockDigitalChannels
  194. elif block['m_isAnalog'] == 1:
  195. # analog channel
  196. if block['m_Mode'] == 1:
  197. # level channel
  198. type_subblock = 'level'
  199. description = Type2_SubBlockLevelChannels
  200. elif block['m_Mode'] == 2:
  201. # external trigger channel
  202. type_subblock = 'external_trigger'
  203. description = Type2_SubBlockExtTriggerChannels
  204. else:
  205. # continuous channel
  206. type_subblock = 'continuous(Mode' \
  207. + str(block['m_Mode']) +')'
  208. description = Type2_SubBlockContinuousChannels
  209. subblock = HeaderReader(fid, description).read_f()
  210. block.update(subblock)
  211. block.update({'type_subblock': type_subblock})
  212. file_blocks.append(block)
  213. pos_block += m_length
  214. fid.seek(pos_block)
  215. # step 2: find the available channels
  216. list_chan = [] # list containing indexes of channel blocks
  217. for ind_block, block in enumerate(file_blocks):
  218. if block['m_TypeBlock'] == '2':
  219. list_chan.append(ind_block)
  220. # step 3: find blocks containing data for the available channels
  221. list_data = [] # list of lists of indexes of data blocks
  222. # corresponding to each channel
  223. for ind_chan, chan in enumerate(list_chan):
  224. list_data.append([])
  225. num_chan = file_blocks[chan]['m_numChannel']
  226. for ind_block, block in enumerate(file_blocks):
  227. if block['m_TypeBlock'] == '5':
  228. if block['m_numChannel'] == num_chan:
  229. list_data[ind_chan].append(ind_block)
  230. # step 4: compute the length (number of samples) of the channels
  231. chan_len = np.zeros(len(list_data), dtype = np.int)
  232. for ind_chan, list_blocks in enumerate(list_data):
  233. for ind_block in list_blocks:
  234. chan_len[ind_chan] += count_samples(
  235. file_blocks[ind_block]['m_length'])
  236. # step 5: find channels for which data are available
  237. ind_valid_chan = np.nonzero(chan_len)[0]
  238. # step 6: load the data
  239. # TODO give the possibility to load data as AnalogSignalArrays
  240. for ind_chan in ind_valid_chan:
  241. list_blocks = list_data[ind_chan]
  242. ind = 0 # index in the data vector
  243. # read time stamp for the beginning of the signal
  244. form = '<l' # reading format
  245. ind_block = list_blocks[0]
  246. count = count_samples(file_blocks[ind_block]['m_length'])
  247. fid.seek(file_blocks[ind_block]['pos']+6+count*2)
  248. buf = fid.read(struct.calcsize(form))
  249. val = struct.unpack(form , buf)
  250. start_index = val[0]
  251. # WARNING: in the following blocks are read supposing taht they
  252. # are all contiguous and sorted in time. I don't know if it's
  253. # always the case. Maybe we should use the time stamp of each
  254. # data block to choose where to put the read data in the array.
  255. if not lazy:
  256. temp_array = np.empty(chan_len[ind_chan], dtype = np.int16)
  257. # NOTE: we could directly create an empty AnalogSignal and
  258. # load the data in it, but it is much faster to load data
  259. # in a temporary numpy array and create the AnalogSignals
  260. # from this temporary array
  261. for ind_block in list_blocks:
  262. count = count_samples(
  263. file_blocks[ind_block]['m_length'])
  264. fid.seek(file_blocks[ind_block]['pos']+6)
  265. temp_array[ind:ind+count] = \
  266. np.fromfile(fid, dtype = np.int16, count = count)
  267. ind += count
  268. sampling_rate = \
  269. file_blocks[list_chan[ind_chan]]['m_SampleRate'] * pq.kHz
  270. t_start = (start_index / sampling_rate).simplified
  271. if lazy:
  272. ana_sig = AnalogSignal([],
  273. sampling_rate = sampling_rate,
  274. t_start = t_start,
  275. name = file_blocks\
  276. [list_chan[ind_chan]]['m_Name'],
  277. file_origin = \
  278. os.path.basename(self.filename),
  279. units = pq.dimensionless)
  280. ana_sig.lazy_shape = chan_len[ind_chan]
  281. else:
  282. ana_sig = AnalogSignal(temp_array,
  283. sampling_rate = sampling_rate,
  284. t_start = t_start,
  285. name = file_blocks\
  286. [list_chan[ind_chan]]['m_Name'],
  287. file_origin = \
  288. os.path.basename(self.filename),
  289. units = pq.dimensionless)
  290. # todo apibreak: create ChannelIndex for each signals
  291. # ana_sig.channel_index = \
  292. # file_blocks[list_chan[ind_chan]]['m_numChannel']
  293. ana_sig.annotate(channel_name = \
  294. file_blocks[list_chan[ind_chan]]['m_Name'])
  295. ana_sig.annotate(channel_type = \
  296. file_blocks[list_chan[ind_chan]]['type_subblock'])
  297. seg.analogsignals.append(ana_sig)
  298. fid.close()
  299. if file_blocks[0]['m_TypeBlock'] == 'h': # this should always be true
  300. blck.rec_datetime = datetime.datetime(\
  301. file_blocks[0]['m_date_year'],
  302. file_blocks[0]['m_date_month'],
  303. file_blocks[0]['m_date_day'],
  304. file_blocks[0]['m_time_hour'],
  305. file_blocks[0]['m_time_minute'],
  306. file_blocks[0]['m_time_second'],
  307. 10000 * file_blocks[0]['m_time_hsecond'])
  308. # the 10000 is here to convert m_time_hsecond from centisecond
  309. # to microsecond
  310. version = file_blocks[0]['m_version']
  311. blck.annotate(alphamap_version = version)
  312. if cascade:
  313. seg.rec_datetime = blck.rec_datetime.replace()
  314. # I couldn't find a simple copy function for datetime,
  315. # using replace without arguments is a twisted way to make a
  316. # copy
  317. seg.annotate(alphamap_version = version)
  318. if cascade:
  319. blck.create_many_to_one_relationship()
  320. return blck
  321. """
  322. Information for special types in [1]:
  323. _dostime_t type definition:
  324. struct dos_time_t
  325. {
  326. unsigned char hour; /* hours (0-23)*/
  327. unsigned char minute; /* minutes (0-59)*/
  328. unsigned char second; /* seconds (0-59) */
  329. unsigned char hsecond; /* seconds/ 100 (0-99)*/
  330. }
  331. _dosdate_t type definition:
  332. struct _dosdate_t
  333. {
  334. unsigned char day; /* day of month( 1-31) */
  335. unsigned char month; /* month (1-12) */
  336. unsigned int year; /* year (1980-2099) */
  337. unsigned char dayofweek; /* day of week (0 = Sunday) */
  338. }
  339. WINDOWPLACEMENT16 type definition (according to WINE source code):
  340. typedef struct
  341. {
  342. UINT16 length;
  343. UINT16 flags;
  344. UINT16 showCmd;
  345. POINT16 ptMinPosition;
  346. POINT16 ptMaxPosition;
  347. RECT16 rcNormalPosition;
  348. } WINDOWPLACEMENT16,*LPNONCLIENTMETRICS16;
  349. """
  350. max_string_len = '32s' # maximal length of variable length strings in the file
  351. # WARNING: I don't know what is the real value here. According to [1] p 139
  352. # it seems that it could be 20. Some tests would be needed to check this.
  353. # WARNING: A cleaner way to handle strings reading is suitable. Currently I
  354. # read a buffer of max_string_len bytes and look for the C "end of string"
  355. # character ('\x00'). It would be better either to read characters until
  356. # reaching '\x00' or to read the exact number of characters needed, if the
  357. # length of a string can be deduced from the lentgh of the block and the number
  358. # of bytes already read (it seems possible, at least for certain block types).
  359. # WARNING: Some test files contains data blocks of type 'b' and they are not
  360. # described in the documentation.
  361. # The name of the keys in the folowing dicts are chosen to match as closely as
  362. # possible the names in document [1]
  363. TypeH_Header = [
  364. ('m_nextBlock','l'),
  365. ('m_version','h'),
  366. ('m_time_hour', 'B'),
  367. ('m_time_minute', 'B'),
  368. ('m_time_second', 'B'),
  369. ('m_time_hsecond', 'B'),
  370. ('m_date_day', 'B'),
  371. ('m_date_month', 'B'),
  372. ('m_date_year', 'H'),
  373. ('m_date_dayofweek', 'B'),
  374. ('blank', 'x'), # one byte blank because of the 2 bytes alignement
  375. ('m_MinimumTime','d'),
  376. ('m_MaximumTime','d')]
  377. Type0_SetBoards = [
  378. ('m_nextBlock','l'),
  379. ('m_BoardCount','h'),
  380. ('m_GroupCount','h'),
  381. ('m_placeMainWindow','x')] # WARNING: unknown type ('x' is wrong)
  382. Type1_Boards = [ # WARNING: needs to be checked
  383. ('m_nextBlock','l'),
  384. ('m_Number','h'),
  385. ('m_countChannel','h'),
  386. ('m_countAnIn','h'),
  387. ('m_countAnOut','h'),
  388. ('m_countDigIn','h'),
  389. ('m_countDigOut','h'),
  390. ('m_TrigCount', 'h'), # not defined in 5.3.3 but appears in 5.5.1 and
  391. # seems to really exist in files
  392. # WARNING: check why 'm_TrigCount is not in the C code [2]
  393. ('m_Amplitude','f'),
  394. ('m_cSampleRate','f'), # sample rate seems to be given in kHz
  395. ('m_Duration','f'),
  396. ('m_nPreTrigmSec','f'),
  397. ('m_nPostTrigmSec','f'),
  398. ('m_TrgMode','h'),
  399. ('m_LevelValue','h'), # after this line, 5.3.3 is wrong,
  400. # check example in 5.5.1 for the right fields
  401. # WARNING: check why the following part is not corrected in the C code [2]
  402. ('m_nSamples','h'),
  403. ('m_fRMS','f'),
  404. ('m_ScaleFactor','f'),
  405. ('m_DapTime','f'),
  406. ('m_nameBoard', max_string_len)]
  407. #('m_DiscMaxValue','h'), # WARNING: should this exist?
  408. #('m_DiscMinValue','h') # WARNING: should this exist?
  409. Type2_DefBlocksChannels = [
  410. # common parameters for all types of channels
  411. ('m_nextBlock','l'),
  412. ('m_isAnalog','h'),
  413. ('m_isInput','h'),
  414. ('m_numChannel','h'),
  415. ('m_numColor','h'),
  416. ('m_Mode','h')]
  417. Type2_SubBlockContinuousChannels = [
  418. # continuous channels parameters
  419. ('blank', '2x'), # WARNING: this is not in the specs but it seems needed
  420. ('m_Amplitude','f'),
  421. ('m_SampleRate','f'),
  422. ('m_ContBlkSize','h'),
  423. ('m_ModeSpike','h'), # WARNING: the C code [2] uses usigned short here
  424. ('m_Duration','f'),
  425. ('m_bAutoScale','h'),
  426. ('m_Name', max_string_len)]
  427. Type2_SubBlockLevelChannels = [ # WARNING: untested
  428. # level channels parameters
  429. ('m_Amplitude','f'),
  430. ('m_SampleRate','f'),
  431. ('m_nSpikeCount','h'),
  432. ('m_ModeSpike','h'),
  433. ('m_nPreTrigmSec','f'),
  434. ('m_nPostTrigmSec','f'),
  435. ('m_LevelValue','h'),
  436. ('m_TrgMode','h'),
  437. ('m_YesRms','h'),
  438. ('m_bAutoScale','h'),
  439. ('m_Name', max_string_len)]
  440. Type2_SubBlockExtTriggerChannels = [ # WARNING: untested
  441. # external trigger channels parameters
  442. ('m_Amplitude','f'),
  443. ('m_SampleRate','f'),
  444. ('m_nSpikeCount','h'),
  445. ('m_ModeSpike','h'),
  446. ('m_nPreTrigmSec','f'),
  447. ('m_nPostTrigmSec','f'),
  448. ('m_TriggerNumber','h'),
  449. ('m_Name', max_string_len)]
  450. Type2_SubBlockDigitalChannels = [
  451. # digital channels parameters
  452. ('m_SampleRate','f'),
  453. ('m_SaveTrigger','h'),
  454. ('m_Duration','f'),
  455. ('m_PreviousStatus','h'), # WARNING: check difference with C code here
  456. ('m_Name', max_string_len)]
  457. Type2_SubBlockUnknownChannels = [
  458. # WARNING: We have a mode that doesn't appear in our spec, so we don't
  459. # know what are the fields.
  460. # It seems that for non-digital channels the beginning is
  461. # similar to continuous channels. Let's hope we're right...
  462. ('blank', '2x'),
  463. ('m_Amplitude','f'),
  464. ('m_SampleRate','f')]
  465. # there are probably other fields after...
  466. Type6_DefBlockTrigger = [ # WARNING: untested
  467. ('m_nextBlock','l'),
  468. ('m_Number','h'),
  469. ('m_countChannel','h'),
  470. ('m_StateChannels','i'),
  471. ('m_numChannel1','h'),
  472. ('m_numChannel2','h'),
  473. ('m_numChannel3','h'),
  474. ('m_numChannel4','h'),
  475. ('m_numChannel5','h'),
  476. ('m_numChannel6','h'),
  477. ('m_numChannel7','h'),
  478. ('m_numChannel8','h'),
  479. ('m_Name','c')]
  480. Type3_DefBlockGroup = [ # WARNING: untested
  481. ('m_nextBlock','l'),
  482. ('m_Number','h'),
  483. ('m_Z_Order','h'),
  484. ('m_countSubGroups','h'),
  485. ('m_placeGroupWindow','x'), # WARNING: unknown type ('x' is wrong)
  486. ('m_NetLoc','h'),
  487. ('m_locatMax','x'), # WARNING: unknown type ('x' is wrong)
  488. ('m_nameGroup','c')]
  489. Type4_DefBlockSubgroup = [ # WARNING: untested
  490. ('m_nextBlock','l'),
  491. ('m_Number','h'),
  492. ('m_TypeOverlap','h'),
  493. ('m_Z_Order','h'),
  494. ('m_countChannel','h'),
  495. ('m_NetLoc','h'),
  496. ('m_location','x'), # WARNING: unknown type ('x' is wrong)
  497. ('m_bIsMaximized','h'),
  498. ('m_numChannel1','h'),
  499. ('m_numChannel2','h'),
  500. ('m_numChannel3','h'),
  501. ('m_numChannel4','h'),
  502. ('m_numChannel5','h'),
  503. ('m_numChannel6','h'),
  504. ('m_numChannel7','h'),
  505. ('m_numChannel8','h'),
  506. ('m_Name','c')]
  507. Type5_DataBlockOneChannel = [
  508. ('m_numChannel','h')]
  509. # WARNING: 'm_numChannel' (called 'm_Number' in 5.4.1 of [1]) is supposed
  510. # to be uint according to 5.4.1 but it seems to be a short in the files
  511. # (or should it be ushort ?)
  512. # WARNING: In 5.1.1 page 121 of [1], they say "Note: 5 is used for demo
  513. # purposes, 7 is used for real data", but looking at some real datafiles,
  514. # it seems that block of type 5 are also used for real data...
  515. Type7_DataBlockMultipleChannels = [ # WARNING: unfinished
  516. ('m_lenHead', 'h'), # WARNING: unknown true type
  517. ('FINT','h')]
  518. # WARNING: there should be data after...
  519. TypeP_DefBlockPeriStimHist = [ # WARNING: untested
  520. ('m_Number_Chan','h'),
  521. ('m_Position','x'), # WARNING: unknown type ('x' is wrong)
  522. ('m_isStatVisible','h'),
  523. ('m_DurationSec','f'),
  524. ('m_Rows','i'),
  525. ('m_DurationSecPre','f'),
  526. ('m_Bins','i'),
  527. ('m_NoTrigger','h')]
  528. TypeF_DefBlockFRTachogram = [ # WARNING: untested
  529. ('m_Number_Chan','h'),
  530. ('m_Position','x'), # WARNING: unknown type ('x' is wrong)
  531. ('m_isStatVisible','h'),
  532. ('m_DurationSec','f'),
  533. ('m_AutoManualScale','i'),
  534. ('m_Max','i')]
  535. TypeR_DefBlockRaster = [ # WARNING: untested
  536. ('m_Number_Chan','h'),
  537. ('m_Position','x'), # WARNING: unknown type ('x' is wrong)
  538. ('m_isStatVisible','h'),
  539. ('m_DurationSec','f'),
  540. ('m_Rows','i'),
  541. ('m_NoTrigger','h')]
  542. TypeI_DefBlockISIHist = [ # WARNING: untested
  543. ('m_Number_Chan','h'),
  544. ('m_Position','x'), # WARNING: unknown type ('x' is wrong)
  545. ('m_isStatVisible','h'),
  546. ('m_DurationSec','f'),
  547. ('m_Bins','i'),
  548. ('m_TypeScale','i')]
  549. Type8_MarkerBlock = [ # WARNING: untested
  550. ('m_Number_Channel','h'),
  551. ('m_Time','l')] # WARNING: check what's the right type here.
  552. # It seems that the size of time_t type depends on the system typedef,
  553. # I put long here but I couldn't check if it is the right type
  554. Type9_ScaleBlock = [ # WARNING: untested
  555. ('m_Number_Channel','h'),
  556. ('m_Scale','f')]
  557. Type_Unknown = []
  558. dict_header_type = {
  559. 'h' : TypeH_Header,
  560. '0' : Type0_SetBoards,
  561. '1' : Type1_Boards,
  562. '2' : Type2_DefBlocksChannels,
  563. '6' : Type6_DefBlockTrigger,
  564. '3' : Type3_DefBlockGroup,
  565. '4' : Type4_DefBlockSubgroup,
  566. '5' : Type5_DataBlockOneChannel,
  567. '7' : Type7_DataBlockMultipleChannels,
  568. 'P' : TypeP_DefBlockPeriStimHist,
  569. 'F' : TypeF_DefBlockFRTachogram,
  570. 'R' : TypeR_DefBlockRaster,
  571. 'I' : TypeI_DefBlockISIHist,
  572. '8' : Type8_MarkerBlock,
  573. '9' : Type9_ScaleBlock
  574. }
  575. class HeaderReader():
  576. def __init__(self,fid ,description ):
  577. self.fid = fid
  578. self.description = description
  579. def read_f(self, offset =None):
  580. if offset is not None :
  581. self.fid.seek(offset)
  582. d = { }
  583. for key, fmt in self.description :
  584. fmt = '<' + fmt # insures use of standard sizes
  585. buf = self.fid.read(struct.calcsize(fmt))
  586. if len(buf) != struct.calcsize(fmt) : return None
  587. val = list(struct.unpack(fmt , buf))
  588. for i, ival in enumerate(val):
  589. if hasattr(ival, 'split'):
  590. val[i] = ival.split('\x00', 1)[0]
  591. if len(val) == 1:
  592. val = val[0]
  593. d[key] = val
  594. return d