intanrawio.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. # -*- coding: utf-8 -*-
  2. """
  3. Support for intan tech rhd and rhs files.
  4. This 2 formats are more or less the same but:
  5. * some variance in headers.
  6. * rhs amplifier is more complexe because the optional DC channel
  7. RHS supported version 1.0
  8. RHD supported version 1.0 1.1 1.2 1.3 2.0
  9. See:
  10. * http://intantech.com/files/Intan_RHD2000_data_file_formats.pdf
  11. * http://intantech.com/files/Intan_RHS2000_data_file_formats.pdf
  12. Author: Samuel Garcia
  13. """
  14. from __future__ import print_function, division, absolute_import
  15. # from __future__ import unicode_literals is not compatible with numpy.dtype both py2 py3
  16. from .baserawio import (BaseRawIO, _signal_channel_dtype, _unit_channel_dtype,
  17. _event_channel_dtype)
  18. import numpy as np
  19. from collections import OrderedDict
  20. from distutils.version import LooseVersion as V
  21. class IntanRawIO(BaseRawIO):
  22. """
  23. """
  24. extensions = ['rhd', 'rhs']
  25. rawmode = 'one-file'
  26. def __init__(self, filename=''):
  27. BaseRawIO.__init__(self)
  28. self.filename = filename
  29. def _source_name(self):
  30. return self.filename
  31. def _parse_header(self):
  32. if self.filename.endswith('.rhs'):
  33. self._global_info, self._ordered_channels, data_dtype,\
  34. header_size, self._block_size = read_rhs(self.filename)
  35. elif self.filename.endswith('.rhd'):
  36. self._global_info, self._ordered_channels, data_dtype,\
  37. header_size, self._block_size = read_rhd(self.filename)
  38. # memmap raw data with the complicated structured dtype
  39. self._raw_data = np.memmap(self.filename, dtype=data_dtype, mode='r', offset=header_size)
  40. # check timestamp continuity
  41. timestamp = self._raw_data['timestamp'].flatten()
  42. assert np.all(np.diff(timestamp) == 1), 'timestamp have gaps'
  43. # signals
  44. sig_channels = []
  45. for c, chan_info in enumerate(self._ordered_channels):
  46. name = chan_info['native_channel_name']
  47. chan_id = c # the chan_id have no meaning in intan
  48. if chan_info['signal_type'] == 20:
  49. # exception for temperature
  50. sig_dtype = 'int16'
  51. else:
  52. sig_dtype = 'uint16'
  53. group_id = 0
  54. sig_channels.append((name, chan_id, chan_info['sampling_rate'],
  55. sig_dtype, chan_info['units'], chan_info['gain'],
  56. chan_info['offset'], chan_info['signal_type']))
  57. sig_channels = np.array(sig_channels, dtype=_signal_channel_dtype)
  58. self._max_sampling_rate = np.max(sig_channels['sampling_rate'])
  59. self._max_sigs_length = self._raw_data.size * self._block_size
  60. # No events
  61. event_channels = []
  62. event_channels = np.array(event_channels, dtype=_event_channel_dtype)
  63. # No spikes
  64. unit_channels = []
  65. unit_channels = np.array(unit_channels, dtype=_unit_channel_dtype)
  66. # fille into header dict
  67. self.header = {}
  68. self.header['nb_block'] = 1
  69. self.header['nb_segment'] = [1]
  70. self.header['signal_channels'] = sig_channels
  71. self.header['unit_channels'] = unit_channels
  72. self.header['event_channels'] = event_channels
  73. self._generate_minimal_annotations()
  74. def _segment_t_start(self, block_index, seg_index):
  75. return 0.
  76. def _segment_t_stop(self, block_index, seg_index):
  77. t_stop = self._max_sigs_length / self._max_sampling_rate
  78. return t_stop
  79. def _get_signal_size(self, block_index, seg_index, channel_indexes):
  80. assert channel_indexes is not None, 'channel_indexes cannot be None, several signal size'
  81. assert np.unique(self.header['signal_channels'][channel_indexes]['group_id']).size == 1
  82. channel_names = self.header['signal_channels'][channel_indexes]['name']
  83. chan_name = channel_names[0]
  84. size = self._raw_data[chan_name].size
  85. return size
  86. def _get_signal_t_start(self, block_index, seg_index, channel_indexes):
  87. return 0.
  88. def _get_analogsignal_chunk(self, block_index, seg_index, i_start, i_stop, channel_indexes):
  89. if i_start is None:
  90. i_start = 0
  91. if i_stop is None:
  92. i_stop = self._get_signal_size(block_index, seg_index, channel_indexes)
  93. if channel_indexes is None:
  94. channel_indexes = slice(None)
  95. channel_names = self.header['signal_channels'][channel_indexes]['name']
  96. shape = self._raw_data[channel_names[0]].shape
  97. # some channel (temperature) have 1D field so shape 1D
  98. # because 1 sample per block
  99. if len(shape) == 2:
  100. # this is the general case with 2D
  101. block_size = shape[1]
  102. block_start = i_start // block_size
  103. block_stop = i_stop // block_size + 1
  104. sl0 = i_start % block_size
  105. sl1 = sl0 + (i_stop - i_start)
  106. sigs_chunk = np.zeros((i_stop - i_start, len(channel_names)), dtype='uint16')
  107. for i, chan_name in enumerate(channel_names):
  108. data_chan = self._raw_data[chan_name]
  109. if len(shape) == 1:
  110. sigs_chunk[:, i] = data_chan[i_start:i_stop]
  111. else:
  112. sigs_chunk[:, i] = data_chan[block_start:block_stop].flatten()[sl0:sl1]
  113. return sigs_chunk
  114. def read_qstring(f):
  115. length = np.fromfile(f, dtype='uint32', count=1)[0]
  116. if length == 0xFFFFFFFF or length == 0:
  117. return ''
  118. txt = f.read(length).decode('utf-16')
  119. return txt
  120. def read_variable_header(f, header):
  121. info = {}
  122. for field_name, field_type in header:
  123. if field_type == 'QString':
  124. field_value = read_qstring(f)
  125. else:
  126. field_value = np.fromfile(f, dtype=field_type, count=1)[0]
  127. info[field_name] = field_value
  128. return info
  129. ###############
  130. # RHS ZONE
  131. rhs_global_header = [
  132. ('magic_number', 'uint32'), # 0xD69127AC
  133. ('major_version', 'int16'),
  134. ('minor_version', 'int16'),
  135. ('sampling_rate', 'float32'),
  136. ('dsp_enabled', 'int16'),
  137. ('actual_dsp_cutoff_frequency', 'float32'),
  138. ('actual_lower_bandwidth', 'float32'),
  139. ('actual_lower_settle_bandwidth', 'float32'),
  140. ('actual_upper_bandwidth', 'float32'),
  141. ('desired_dsp_cutoff_frequency', 'float32'),
  142. ('desired_lower_bandwidth', 'float32'),
  143. ('desired_lower_settle_bandwidth', 'float32'),
  144. ('desired_upper_bandwidth', 'float32'),
  145. ('notch_filter_mode', 'int16'),
  146. ('desired_impedance_test_frequency', 'float32'),
  147. ('actual_impedance_test_frequency', 'float32'),
  148. ('amp_settle_mode', 'int16'),
  149. ('charge_recovery_mode', 'int16'),
  150. ('stim_step_size', 'float32'),
  151. ('recovery_current_limit', 'float32'),
  152. ('recovery_target_voltage', 'float32'),
  153. ('note1', 'QString'),
  154. ('note2', 'QString'),
  155. ('note3', 'QString'),
  156. ('dc_amplifier_data_saved', 'int16'),
  157. ('board_mode', 'int16'),
  158. ('ref_channel_name', 'QString'),
  159. ('nb_signal_group', 'int16'),
  160. ]
  161. rhs_signal_group_header = [
  162. ('signal_group_name', 'QString'),
  163. ('signal_group_prefix', 'QString'),
  164. ('signal_group_enabled', 'int16'),
  165. ('channel_num', 'int16'),
  166. ('amplified_channel_num', 'int16'),
  167. ]
  168. rhs_signal_channel_header = [
  169. ('native_channel_name', 'QString'),
  170. ('custom_channel_name', 'QString'),
  171. ('native_order', 'int16'),
  172. ('custom_order', 'int16'),
  173. ('signal_type', 'int16'),
  174. ('channel_enabled', 'int16'),
  175. ('chip_channel_num', 'int16'),
  176. ('command_stream', 'int16'),
  177. ('board_stream_num', 'int16'),
  178. ('spike_scope_trigger_mode', 'int16'),
  179. ('spike_scope_voltage_thresh', 'int16'),
  180. ('spike_scope_digital_trigger_channel', 'int16'),
  181. ('spike_scope_digital_edge_polarity', 'int16'),
  182. ('electrode_impedance_magnitude', 'float32'),
  183. ('electrode_impedance_phase', 'float32'),
  184. ]
  185. def read_rhs(filename):
  186. BLOCK_SIZE = 128 # sample per block
  187. with open(filename, mode='rb') as f:
  188. global_info = read_variable_header(f, rhs_global_header)
  189. channels_by_type = {k: [] for k in [0, 3, 4, 5, 6]}
  190. for g in range(global_info['nb_signal_group']):
  191. group_info = read_variable_header(f, rhs_signal_group_header)
  192. if bool(group_info['signal_group_enabled']):
  193. for c in range(group_info['channel_num']):
  194. chan_info = read_variable_header(f, rhs_signal_channel_header)
  195. assert chan_info['signal_type'] not in (1, 2)
  196. if bool(chan_info['channel_enabled']):
  197. channels_by_type[chan_info['signal_type']].append(chan_info)
  198. header_size = f.tell()
  199. sr = global_info['sampling_rate']
  200. # construct dtype by re-ordering channels by types
  201. ordered_channels = []
  202. data_dtype = [('timestamp', 'int32', BLOCK_SIZE)]
  203. # 0: RHS2000 amplifier channel.
  204. for chan_info in channels_by_type[0]:
  205. name = chan_info['native_channel_name']
  206. chan_info['sampling_rate'] = sr
  207. chan_info['units'] = 'uV'
  208. chan_info['gain'] = 0.195
  209. chan_info['offset'] = -32768 * 0.195
  210. ordered_channels.append(chan_info)
  211. data_dtype += [(name, 'uint16', BLOCK_SIZE)]
  212. if bool(global_info['dc_amplifier_data_saved']):
  213. for chan_info in channels_by_type[0]:
  214. name = chan_info['native_channel_name']
  215. chan_info_dc = dict(chan_info)
  216. chan_info_dc['native_channel_name'] = name + '_DC'
  217. chan_info_dc['sampling_rate'] = sr
  218. chan_info_dc['units'] = 'mV'
  219. chan_info_dc['gain'] = 19.23
  220. chan_info_dc['offset'] = -512 * 19.23
  221. chan_info_dc['signal_type'] = 10 # put it in another group
  222. ordered_channels.append(chan_info_dc)
  223. data_dtype += [(name + '_DC', 'uint16', BLOCK_SIZE)]
  224. for chan_info in channels_by_type[0]:
  225. name = chan_info['native_channel_name']
  226. chan_info_stim = dict(chan_info)
  227. chan_info_stim['native_channel_name'] = name + '_STIM'
  228. chan_info_stim['sampling_rate'] = sr
  229. # stim channel are coplicated because they are coded
  230. # with bits, they do not fit the gain/offset rawio strategy
  231. chan_info_stim['units'] = ''
  232. chan_info_stim['gain'] = 1.
  233. chan_info_stim['offset'] = 0.
  234. chan_info_stim['signal_type'] = 11 # put it in another group
  235. ordered_channels.append(chan_info_stim)
  236. data_dtype += [(name + '_STIM', 'uint16', BLOCK_SIZE)]
  237. # 3: Analog input channel.
  238. # 4: Analog output channel.
  239. for sig_type in [3, 4, ]:
  240. for chan_info in channels_by_type[sig_type]:
  241. name = chan_info['native_channel_name']
  242. chan_info['sampling_rate'] = sr
  243. chan_info['units'] = 'V'
  244. chan_info['gain'] = 0.0003125
  245. chan_info['offset'] = -32768 * 0.0003125
  246. ordered_channels.append(chan_info)
  247. data_dtype += [(name, 'uint16', BLOCK_SIZE)]
  248. # 5: Digital input channel.
  249. # 6: Digital output channel.
  250. for sig_type in [5, 6]:
  251. # at the moment theses channel are not in sig channel list
  252. # but they are in the raw memamp
  253. if len(channels_by_type[sig_type]) > 0:
  254. name = {5: 'DIGITAL-IN', 6: 'DIGITAL-OUT'}[sig_type]
  255. data_dtype += [(name, 'uint16', BLOCK_SIZE)]
  256. return global_info, ordered_channels, data_dtype, header_size, BLOCK_SIZE
  257. ###############
  258. # RHD ZONE
  259. rhd_global_header_base = [
  260. ('magic_number', 'uint32'), # 0xC6912702
  261. ('major_version', 'int16'),
  262. ('minor_version', 'int16'),
  263. ]
  264. rhd_global_header_part1 = [
  265. ('sampling_rate', 'float32'),
  266. ('dsp_enabled', 'int16'),
  267. ('actual_dsp_cutoff_frequency', 'float32'),
  268. ('actual_lower_bandwidth', 'float32'),
  269. ('actual_upper_bandwidth', 'float32'),
  270. ('desired_dsp_cutoff_frequency', 'float32'),
  271. ('desired_lower_bandwidth', 'float32'),
  272. ('desired_upper_bandwidth', 'float32'),
  273. ('notch_filter_mode', 'int16'),
  274. ('desired_impedance_test_frequency', 'float32'),
  275. ('actual_impedance_test_frequency', 'float32'),
  276. ('note1', 'QString'),
  277. ('note2', 'QString'),
  278. ('note3', 'QString'),
  279. ]
  280. rhd_global_header_v11 = [
  281. ('num_temp_sensor_channels', 'int16'),
  282. ]
  283. rhd_global_header_v13 = [
  284. ('eval_board_mode', 'int16'),
  285. ]
  286. rhd_global_header_v20 = [
  287. ('reference_channel', 'QString'),
  288. ]
  289. rhd_global_header_final = [
  290. ('nb_signal_group', 'int16'),
  291. ]
  292. rhd_signal_group_header = [
  293. ('signal_group_name', 'QString'),
  294. ('signal_group_prefix', 'QString'),
  295. ('signal_group_enabled', 'int16'),
  296. ('channel_num', 'int16'),
  297. ('amplified_channel_num', 'int16'),
  298. ]
  299. rhd_signal_channel_header = [
  300. ('native_channel_name', 'QString'),
  301. ('custom_channel_name', 'QString'),
  302. ('native_order', 'int16'),
  303. ('custom_order', 'int16'),
  304. ('signal_type', 'int16'),
  305. ('channel_enabled', 'int16'),
  306. ('chip_channel_num', 'int16'),
  307. ('board_stream_num', 'int16'),
  308. ('spike_scope_trigger_mode', 'int16'),
  309. ('spike_scope_voltage_thresh', 'int16'),
  310. ('spike_scope_digital_trigger_channel', 'int16'),
  311. ('spike_scope_digital_edge_polarity', 'int16'),
  312. ('electrode_impedance_magnitude', 'float32'),
  313. ('electrode_impedance_phase', 'float32'),
  314. ]
  315. def read_rhd(filename):
  316. with open(filename, mode='rb') as f:
  317. global_info = read_variable_header(f, rhd_global_header_base)
  318. version = V('{major_version}.{minor_version}'.format(**global_info))
  319. # the header size depend on the version :-(
  320. header = list(rhd_global_header_part1) # make a copy
  321. if version >= '1.1':
  322. header = header + rhd_global_header_v11
  323. else:
  324. global_info['num_temp_sensor_channels'] = 0
  325. if version >= '1.3':
  326. header = header + rhd_global_header_v13
  327. else:
  328. global_info['eval_board_mode'] = 0
  329. if version >= '2.0':
  330. header = header + rhd_global_header_v20
  331. else:
  332. global_info['reference_channel'] = ''
  333. header = header + rhd_global_header_final
  334. global_info.update(read_variable_header(f, header))
  335. # read channel group and channel header
  336. channels_by_type = {k: [] for k in [0, 1, 2, 3, 4, 5]}
  337. for g in range(global_info['nb_signal_group']):
  338. group_info = read_variable_header(f, rhd_signal_group_header)
  339. if bool(group_info['signal_group_enabled']):
  340. for c in range(group_info['channel_num']):
  341. chan_info = read_variable_header(f, rhd_signal_channel_header)
  342. if bool(chan_info['channel_enabled']):
  343. channels_by_type[chan_info['signal_type']].append(chan_info)
  344. header_size = f.tell()
  345. sr = global_info['sampling_rate']
  346. # construct the data block dtype and reorder channels
  347. if version >= '2.0':
  348. BLOCK_SIZE = 128
  349. else:
  350. BLOCK_SIZE = 60 # 256 channels
  351. ordered_channels = []
  352. if version >= '1.2':
  353. data_dtype = [('timestamp', 'int32', BLOCK_SIZE)]
  354. else:
  355. data_dtype = [('timestamp', 'uint32', BLOCK_SIZE)]
  356. # 0: RHD2000 amplifier channel
  357. for chan_info in channels_by_type[0]:
  358. name = chan_info['native_channel_name']
  359. chan_info['sampling_rate'] = sr
  360. chan_info['units'] = 'uV'
  361. chan_info['gain'] = 0.195
  362. chan_info['offset'] = -32768 * 0.195
  363. ordered_channels.append(chan_info)
  364. data_dtype += [(name, 'uint16', BLOCK_SIZE)]
  365. # 1: RHD2000 auxiliary input channel
  366. for chan_info in channels_by_type[1]:
  367. name = chan_info['native_channel_name']
  368. chan_info['sampling_rate'] = sr / 4.
  369. chan_info['units'] = 'V'
  370. chan_info['gain'] = 0.0000374
  371. chan_info['offset'] = 0.
  372. ordered_channels.append(chan_info)
  373. data_dtype += [(name, 'uint16', BLOCK_SIZE // 4)]
  374. # 2: RHD2000 supply voltage channel
  375. for chan_info in channels_by_type[2]:
  376. name = chan_info['native_channel_name']
  377. chan_info['sampling_rate'] = sr / BLOCK_SIZE
  378. chan_info['units'] = 'V'
  379. chan_info['gain'] = 0.0000748
  380. chan_info['offset'] = 0.
  381. ordered_channels.append(chan_info)
  382. data_dtype += [(name, 'uint16')]
  383. # temperature is not an official channel in the header
  384. for i in range(global_info['num_temp_sensor_channels']):
  385. name = 'temperature_{}'.format(i)
  386. chan_info = {'native_channel_name': name, 'signal_type': 20}
  387. chan_info['sampling_rate'] = sr / BLOCK_SIZE
  388. chan_info['units'] = 'Celsius'
  389. chan_info['gain'] = 0.001
  390. chan_info['offset'] = 0.
  391. ordered_channels.append(chan_info)
  392. data_dtype += [(name, 'int16')]
  393. # 3: USB board ADC input channel
  394. for chan_info in channels_by_type[3]:
  395. name = chan_info['native_channel_name']
  396. chan_info['sampling_rate'] = sr
  397. chan_info['units'] = 'V'
  398. if global_info['eval_board_mode'] == 0:
  399. chan_info['gain'] = 0.000050354
  400. chan_info['offset'] = 0.
  401. elif global_info['eval_board_mode'] == 1:
  402. chan_info['gain'] = 0.00015259
  403. chan_info['offset'] = -32768 * 0.00015259
  404. elif global_info['eval_board_mode'] == 13:
  405. chan_info['gain'] = 0.0003125
  406. chan_info['offset'] = -32768 * 0.0003125
  407. ordered_channels.append(chan_info)
  408. data_dtype += [(name, 'uint16', BLOCK_SIZE)]
  409. # 4: USB board digital input channel
  410. # 5: USB board digital output channel
  411. for sig_type in [4, 5]:
  412. # at the moment theses channel are not in sig channel list
  413. # but they are in the raw memamp
  414. if len(channels_by_type[sig_type]) > 0:
  415. name = {4: 'DIGITAL-IN', 5: 'DIGITAL-OUT'}[sig_type]
  416. data_dtype += [(name, 'uint16', BLOCK_SIZE)]
  417. return global_info, ordered_channels, data_dtype, header_size, BLOCK_SIZE