baserawio.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. # -*- coding: utf-8 -*-
  2. """
  3. baserawio
  4. ======
  5. Classes
  6. -------
  7. BaseRawIO
  8. abstract class which should be overridden to write a RawIO.
  9. RawIO is a new API in neo that is supposed to acces as fast as possible
  10. raw data. All IO with theses carractéristics should/could be rewritten:
  11. * internally use of memmap (or hdf5)
  12. * reading header is quite cheap (not read all the file)
  13. * neo tree object is symetric and logical: same channel/units/event
  14. along all block and segments.
  15. So this handle **only** one simplified but very frequent case of dataset:
  16. * Only one channel set for AnalogSignal (aka ChannelIndex) stable along Segment
  17. * Only one channel set for SpikeTrain (aka Unit) stable along Segment
  18. * AnalogSignal have all the same sampling_rate acroos all Segment
  19. * t_start/t_stop are the same for many object (SpikeTrain, Event) inside a Segment
  20. * AnalogSignal should all have the same sampling_rate otherwise the won't be read
  21. a the same time. So signal_group_mode=='split-all' in BaseFromRaw
  22. An helper class `neo.io.basefromrawio.BaseFromRaw` should transform a RawIO to
  23. neo legacy IO from free.
  24. With this API the IO have an attributes `header` with necessary keys.
  25. See ExampleRawIO as example.
  26. BaseRawIO implement a possible presistent cache system that can be used
  27. by some IOs to avoid very long parse_header(). The idea is that some variable
  28. or vector can be store somewhere (near the fiel, /tmp, any path)
  29. """
  30. # from __future__ import unicode_literals, print_function, division, absolute_import
  31. from __future__ import print_function, division, absolute_import
  32. import logging
  33. import numpy as np
  34. import os
  35. import sys
  36. from neo import logging_handler
  37. try:
  38. import joblib
  39. HAVE_JOBLIB = True
  40. except ImportError:
  41. HAVE_JOBLIB = False
  42. possible_raw_modes = ['one-file', 'multi-file', 'one-dir', ] # 'multi-dir', 'url', 'other'
  43. error_header = 'Header is not read yet, do parse_header() first'
  44. _signal_channel_dtype = [
  45. ('name', 'U64'),
  46. ('id', 'int64'),
  47. ('sampling_rate', 'float64'),
  48. ('dtype', 'U16'),
  49. ('units', 'U64'),
  50. ('gain', 'float64'),
  51. ('offset', 'float64'),
  52. ('group_id', 'int64'),
  53. ]
  54. _common_sig_characteristics = ['sampling_rate', 'dtype', 'group_id']
  55. _unit_channel_dtype = [
  56. ('name', 'U64'),
  57. ('id', 'U64'),
  58. # for waveform
  59. ('wf_units', 'U64'),
  60. ('wf_gain', 'float64'),
  61. ('wf_offset', 'float64'),
  62. ('wf_left_sweep', 'int64'),
  63. ('wf_sampling_rate', 'float64'),
  64. ]
  65. _event_channel_dtype = [
  66. ('name', 'U64'),
  67. ('id', 'U64'),
  68. ('type', 'S5'), # epoch ot event
  69. ]
  70. class BaseRawIO(object):
  71. """
  72. Generic class to handle.
  73. """
  74. name = 'BaseIO'
  75. description = ''
  76. extensions = []
  77. rawmode = None # one key in possible_raw_modes
  78. def __init__(self, use_cache=False, cache_path='same_as_resource', **kargs):
  79. """
  80. When rawmode=='one-file' kargs MUST contains 'filename' the filename
  81. When rawmode=='multi-file' kargs MUST contains 'filename' one of the filenames.
  82. When rawmode=='one-dir' kargs MUST contains 'dirname' the dirname.
  83. """
  84. # create a logger for the IO class
  85. fullname = self.__class__.__module__ + '.' + self.__class__.__name__
  86. self.logger = logging.getLogger(fullname)
  87. # create a logger for 'neo' and add a handler to it if it doesn't
  88. # have one already.
  89. # (it will also not add one if the root logger has a handler)
  90. corename = self.__class__.__module__.split('.')[0]
  91. corelogger = logging.getLogger(corename)
  92. rootlogger = logging.getLogger()
  93. if not corelogger.handlers and not rootlogger.handlers:
  94. corelogger.addHandler(logging_handler)
  95. self.use_cache = use_cache
  96. if use_cache:
  97. assert HAVE_JOBLIB, 'You need to install joblib for cache'
  98. self.setup_cache(cache_path)
  99. else:
  100. self._cache = None
  101. self.header = None
  102. def parse_header(self):
  103. """
  104. This must parse the file header to get all stuff for fast later one.
  105. This must contain
  106. self.header['nb_block']
  107. self.header['nb_segment']
  108. self.header['signal_channels']
  109. self.header['units_channels']
  110. self.header['event_channels']
  111. """
  112. self._parse_header()
  113. self._group_signal_channel_characteristics()
  114. def source_name(self):
  115. """Return fancy name of file source"""
  116. return self._source_name()
  117. def __repr__(self):
  118. txt = '{}: {}\n'.format(self.__class__.__name__, self.source_name())
  119. if self.header is not None:
  120. nb_block = self.block_count()
  121. txt += 'nb_block: {}\n'.format(nb_block)
  122. nb_seg = [self.segment_count(i) for i in range(nb_block)]
  123. txt += 'nb_segment: {}\n'.format(nb_seg)
  124. for k in ('signal_channels', 'unit_channels', 'event_channels'):
  125. ch = self.header[k]
  126. if len(ch) > 8:
  127. chantxt = "[{} ... {}]".format(', '.join(e for e in ch['name'][:4]),
  128. ' '.join(e for e in ch['name'][-4:]))
  129. else:
  130. chantxt = "[{}]".format(', '.join(e for e in ch['name']))
  131. txt += '{}: {}\n'.format(k, chantxt)
  132. return txt
  133. def _generate_minimal_annotations(self):
  134. """
  135. Helper function that generate a nested dict
  136. of all annotations.
  137. must be called when theses are Ok:
  138. * block_count()
  139. * segment_count()
  140. * signal_channels_count()
  141. * unit_channels_count()
  142. * event_channels_count()
  143. Usage:
  144. raw_annotations['blocks'][block_index] = { 'nickname' : 'super block', 'segments' : ...}
  145. raw_annotations['blocks'][block_index] = { 'nickname' : 'super block', 'segments' : ...}
  146. raw_annotations['blocks'][block_index]['segments'][seg_index]['signals'][channel_index] = {'nickname': 'super channel'}
  147. raw_annotations['blocks'][block_index]['segments'][seg_index]['units'][unit_index] = {'nickname': 'super neuron'}
  148. raw_annotations['blocks'][block_index]['segments'][seg_index]['events'][ev_chan] = {'nickname': 'super trigger'}
  149. Theses annotations will be used at the neo.io API directly in objects.
  150. Standard annotation like name/id/file_origin are already generated here.
  151. """
  152. signal_channels = self.header['signal_channels']
  153. unit_channels = self.header['unit_channels']
  154. event_channels = self.header['event_channels']
  155. a = {'blocks': [], 'signal_channels': [], 'unit_channels': [], 'event_channels': []}
  156. for block_index in range(self.block_count()):
  157. d = {'segments': []}
  158. d['file_origin'] = self.source_name()
  159. a['blocks'].append(d)
  160. for seg_index in range(self.segment_count(block_index)):
  161. d = {'signals': [], 'units': [], 'events': []}
  162. d['file_origin'] = self.source_name()
  163. a['blocks'][block_index]['segments'].append(d)
  164. for c in range(signal_channels.size):
  165. # use for AnalogSignal.annotations
  166. d = {}
  167. d['name'] = signal_channels['name'][c]
  168. d['channel_id'] = signal_channels['id'][c]
  169. a['blocks'][block_index]['segments'][seg_index]['signals'].append(d)
  170. for c in range(unit_channels.size):
  171. # use for SpikeTrain.annotations
  172. d = {}
  173. d['name'] = unit_channels['name'][c]
  174. d['id'] = unit_channels['id'][c]
  175. a['blocks'][block_index]['segments'][seg_index]['units'].append(d)
  176. for c in range(event_channels.size):
  177. # use for Event.annotations
  178. d = {}
  179. d['name'] = event_channels['name'][c]
  180. d['id'] = event_channels['id'][c]
  181. d['file_origin'] = self._source_name()
  182. a['blocks'][block_index]['segments'][seg_index]['events'].append(d)
  183. for c in range(signal_channels.size):
  184. # use for ChannelIndex.annotations
  185. d = {}
  186. d['name'] = signal_channels['name'][c]
  187. d['channel_id'] = signal_channels['id'][c]
  188. d['file_origin'] = self._source_name()
  189. a['signal_channels'].append(d)
  190. for c in range(unit_channels.size):
  191. # use for Unit.annotations
  192. d = {}
  193. d['name'] = unit_channels['name'][c]
  194. d['id'] = unit_channels['id'][c]
  195. d['file_origin'] = self._source_name()
  196. a['unit_channels'].append(d)
  197. for c in range(event_channels.size):
  198. # not used in neo.io at the moment could usefull one day
  199. d = {}
  200. d['name'] = event_channels['name'][c]
  201. d['id'] = event_channels['id'][c]
  202. d['file_origin'] = self._source_name()
  203. a['event_channels'].append(d)
  204. self.raw_annotations = a
  205. def _raw_annotate(self, obj_name, chan_index=0, block_index=0, seg_index=0, **kargs):
  206. """
  207. Annotate a object in the list/dict tree annotations.
  208. """
  209. bl_annotations = self.raw_annotations['blocks'][block_index]
  210. seg_annotations = bl_annotations['segments'][seg_index]
  211. if obj_name == 'blocks':
  212. bl_annotations.update(kargs)
  213. elif obj_name == 'segments':
  214. seg_annotations.update(kargs)
  215. elif obj_name in ['signals', 'events', 'units']:
  216. obj_annotations = seg_annotations[obj_name][chan_index]
  217. obj_annotations.update(kargs)
  218. elif obj_name in ['signal_channels', 'unit_channels', 'event_channel']:
  219. obj_annotations = self.raw_annotations[obj_name][chan_index]
  220. obj_annotations.update(kargs)
  221. def _repr_annotations(self):
  222. txt = 'Raw annotations\n'
  223. for block_index in range(self.block_count()):
  224. bl_a = self.raw_annotations['blocks'][block_index]
  225. txt += '*Block {}\n'.format(block_index)
  226. for k, v in bl_a.items():
  227. if k in ('segments',):
  228. continue
  229. txt += ' -{}: {}\n'.format(k, v)
  230. for seg_index in range(self.segment_count(block_index)):
  231. seg_a = bl_a['segments'][seg_index]
  232. txt += ' *Segment {}\n'.format(seg_index)
  233. for k, v in seg_a.items():
  234. if k in ('signals', 'units', 'events',):
  235. continue
  236. txt += ' -{}: {}\n'.format(k, v)
  237. for child in ('signals', 'units', 'events'):
  238. n = self.header[child[:-1] + '_channels'].shape[0]
  239. for c in range(n):
  240. neo_name = {'signals': 'AnalogSignal',
  241. 'units': 'SpikeTrain', 'events': 'Event/Epoch'}[child]
  242. txt += ' *{} {}\n'.format(neo_name, c)
  243. child_a = seg_a[child][c]
  244. for k, v in child_a.items():
  245. txt += ' -{}: {}\n'.format(k, v)
  246. return txt
  247. def print_annotations(self):
  248. """Print formated raw_annotations"""
  249. print(self._repr_annotations())
  250. def block_count(self):
  251. """return number of blocks"""
  252. return self.header['nb_block']
  253. def segment_count(self, block_index):
  254. """return number of segment for a given block"""
  255. return self.header['nb_segment'][block_index]
  256. def signal_channels_count(self):
  257. """Return the number of signal channel.
  258. Same allong all block and Segment.
  259. """
  260. return len(self.header['signal_channels'])
  261. def unit_channels_count(self):
  262. """Return the number of unit (aka spike) channel.
  263. Same allong all block and Segment.
  264. """
  265. return len(self.header['unit_channels'])
  266. def event_channels_count(self):
  267. """Return the number of event/epoch channel.
  268. Same allong all block and Segment.
  269. """
  270. return len(self.header['event_channels'])
  271. def segment_t_start(self, block_index, seg_index):
  272. """Global t_start of a Segment in s. shared by all objects except
  273. for AnalogSignal.
  274. """
  275. return self._segment_t_start(block_index, seg_index)
  276. def segment_t_stop(self, block_index, seg_index):
  277. """Global t_start of a Segment in s. shared by all objects except
  278. for AnalogSignal.
  279. """
  280. return self._segment_t_stop(block_index, seg_index)
  281. ###
  282. # signal and channel zone
  283. def _group_signal_channel_characteristics(self):
  284. """
  285. Usefull for few IOs (TdtrawIO, NeuroExplorerRawIO, ...).
  286. Group signals channels by same characteristics:
  287. * sampling_rate (global along block and segment)
  288. * group_id (explicite channel group)
  289. If all channels have the same characteristics them
  290. `get_analogsignal_chunk` can be call wihtout restriction.
  291. If not then **channel_indexes** must be specified
  292. in `get_analogsignal_chunk` and only channels with same
  293. caracteristics can be read at the same time.
  294. This is usefull for some IO than
  295. have internally several signals channels familly.
  296. For many RawIO all channels have the same
  297. sampling_rate/size/t_start. In that cases, internal flag
  298. **self._several_channel_groups will be set to False, so
  299. `get_analogsignal_chunk(..)` won't suffer in performance.
  300. Note that at neo.io level this have an impact on
  301. `signal_group_mode`. 'split-all' will work in any situation
  302. But grouping channel in the same AnalogSignal
  303. with 'group-by-XXX' will depend on common characteristics
  304. of course.
  305. """
  306. characteristics = self.header['signal_channels'][_common_sig_characteristics]
  307. unique_characteristics = np.unique(characteristics)
  308. if len(unique_characteristics) == 1:
  309. self._several_channel_groups = False
  310. else:
  311. self._several_channel_groups = True
  312. def _check_common_characteristics(self, channel_indexes):
  313. """
  314. Usefull for few IOs (TdtrawIO, NeuroExplorerRawIO, ...).
  315. Check is a set a signal channel_indexes share common
  316. characteristics (**sampling_rate/t_start/size**)
  317. Usefull only when RawIO propose differents channels groups
  318. with differents sampling_rate for instance.
  319. """
  320. # ~ print('_check_common_characteristics', channel_indexes)
  321. assert channel_indexes is not None, \
  322. 'You must specify channel_indexes'
  323. characteristics = self.header['signal_channels'][_common_sig_characteristics]
  324. # ~ print(characteristics[channel_indexes])
  325. assert np.unique(characteristics[channel_indexes]).size == 1, \
  326. 'This channel set have differents characteristics'
  327. def get_group_channel_indexes(self):
  328. """
  329. Usefull for few IOs (TdtrawIO, NeuroExplorerRawIO, ...).
  330. Return a list of channel_indexes than have same characteristics
  331. """
  332. if self._several_channel_groups:
  333. characteristics = self.header['signal_channels'][_common_sig_characteristics]
  334. unique_characteristics = np.unique(characteristics)
  335. channel_indexes_list = []
  336. for e in unique_characteristics:
  337. channel_indexes, = np.nonzero(characteristics == e)
  338. channel_indexes_list.append(channel_indexes)
  339. return channel_indexes_list
  340. else:
  341. return [None]
  342. def channel_name_to_index(self, channel_names):
  343. """
  344. Transform channel_names to channel_indexes.
  345. Based on self.header['signal_channels']
  346. """
  347. ch = self.header['signal_channels']
  348. channel_indexes, = np.nonzero(np.in1d(ch['name'], channel_names))
  349. assert len(channel_indexes) == len(channel_names), 'not match'
  350. return channel_indexes
  351. def channel_id_to_index(self, channel_ids):
  352. """
  353. Transform channel_ids to channel_indexes.
  354. Based on self.header['signal_channels']
  355. """
  356. ch = self.header['signal_channels']
  357. channel_indexes, = np.nonzero(np.in1d(ch['id'], channel_ids))
  358. assert len(channel_indexes) == len(channel_ids), 'not match'
  359. return channel_indexes
  360. def _get_channel_indexes(self, channel_indexes, channel_names, channel_ids):
  361. """
  362. select channel_indexes from channel_indexes/channel_names/channel_ids
  363. depending which is not None
  364. """
  365. if channel_indexes is None and channel_names is not None:
  366. channel_indexes = self.channel_name_to_index(channel_names)
  367. if channel_indexes is None and channel_ids is not None:
  368. channel_indexes = self.channel_id_to_index(channel_ids)
  369. return channel_indexes
  370. def get_signal_size(self, block_index, seg_index, channel_indexes=None):
  371. if self._several_channel_groups:
  372. self._check_common_characteristics(channel_indexes)
  373. return self._get_signal_size(block_index, seg_index, channel_indexes)
  374. def get_signal_t_start(self, block_index, seg_index, channel_indexes=None):
  375. if self._several_channel_groups:
  376. self._check_common_characteristics(channel_indexes)
  377. return self._get_signal_t_start(block_index, seg_index, channel_indexes)
  378. def get_signal_sampling_rate(self, channel_indexes=None):
  379. if self._several_channel_groups:
  380. self._check_common_characteristics(channel_indexes)
  381. chan_index0 = channel_indexes[0]
  382. else:
  383. chan_index0 = 0
  384. sr = self.header['signal_channels'][chan_index0]['sampling_rate']
  385. return float(sr)
  386. def get_analogsignal_chunk(self, block_index=0, seg_index=0, i_start=None, i_stop=None,
  387. channel_indexes=None, channel_names=None, channel_ids=None):
  388. """
  389. Return a chunk of raw signal.
  390. """
  391. channel_indexes = self._get_channel_indexes(channel_indexes, channel_names, channel_ids)
  392. if self._several_channel_groups:
  393. self._check_common_characteristics(channel_indexes)
  394. raw_chunk = self._get_analogsignal_chunk(
  395. block_index, seg_index, i_start, i_stop, channel_indexes)
  396. return raw_chunk
  397. def rescale_signal_raw_to_float(self, raw_signal, dtype='float32',
  398. channel_indexes=None, channel_names=None, channel_ids=None):
  399. channel_indexes = self._get_channel_indexes(channel_indexes, channel_names, channel_ids)
  400. if channel_indexes is None:
  401. channel_indexes = slice(None)
  402. channels = self.header['signal_channels'][channel_indexes]
  403. float_signal = raw_signal.astype(dtype)
  404. if np.any(channels['gain'] != 1.):
  405. float_signal *= channels['gain']
  406. if np.any(channels['offset'] != 0.):
  407. float_signal += channels['offset']
  408. return float_signal
  409. # spiketrain and unit zone
  410. def spike_count(self, block_index=0, seg_index=0, unit_index=0):
  411. return self._spike_count(block_index, seg_index, unit_index)
  412. def get_spike_timestamps(self, block_index=0, seg_index=0, unit_index=0,
  413. t_start=None, t_stop=None):
  414. """
  415. The timestamp is as close to the format itself. Sometimes float/int32/int64.
  416. Sometimes it is the index on the signal but not always.
  417. The conversion to second or index_on_signal is done outside here.
  418. t_start/t_sop are limits in seconds.
  419. """
  420. timestamp = self._get_spike_timestamps(block_index, seg_index, unit_index, t_start, t_stop)
  421. return timestamp
  422. def rescale_spike_timestamp(self, spike_timestamps, dtype='float64'):
  423. """
  424. Rescale spike timestamps to second
  425. """
  426. return self._rescale_spike_timestamp(spike_timestamps, dtype)
  427. # spiketrain waveform zone
  428. def get_spike_raw_waveforms(self, block_index=0, seg_index=0, unit_index=0,
  429. t_start=None, t_stop=None):
  430. wf = self._get_spike_raw_waveforms(block_index, seg_index, unit_index, t_start, t_stop)
  431. return wf
  432. def rescale_waveforms_to_float(self, raw_waveforms, dtype='float32', unit_index=0):
  433. wf_gain = self.header['unit_channels']['wf_gain'][unit_index]
  434. wf_offset = self.header['unit_channels']['wf_offset'][unit_index]
  435. float_waveforms = raw_waveforms.astype(dtype)
  436. if wf_gain != 1.:
  437. float_waveforms *= wf_gain
  438. if wf_offset != 0.:
  439. float_waveforms += wf_offset
  440. return float_waveforms
  441. # event and epoch zone
  442. def event_count(self, block_index=0, seg_index=0, event_channel_index=0):
  443. return self._event_count(block_index, seg_index, event_channel_index)
  444. def get_event_timestamps(self, block_index=0, seg_index=0, event_channel_index=0,
  445. t_start=None, t_stop=None):
  446. """
  447. The timestamp is as close to the format itself. Sometimes float/int32/int64.
  448. Sometimes it is the index on the signal but not always.
  449. The conversion to second or index_on_signal is done outside here.
  450. t_start/t_sop are limits in seconds.
  451. returns
  452. timestamp
  453. labels
  454. durations
  455. """
  456. timestamp, durations, labels = self._get_event_timestamps(
  457. block_index, seg_index, event_channel_index, t_start, t_stop)
  458. return timestamp, durations, labels
  459. def rescale_event_timestamp(self, event_timestamps, dtype='float64'):
  460. """
  461. Rescale event timestamps to s
  462. """
  463. return self._rescale_event_timestamp(event_timestamps, dtype)
  464. def rescale_epoch_duration(self, raw_duration, dtype='float64'):
  465. """
  466. Rescale epoch raw duration to s
  467. """
  468. return self._rescale_epoch_duration(raw_duration, dtype)
  469. def setup_cache(self, cache_path, **init_kargs):
  470. if self.rawmode in ('one-file', 'multi-file'):
  471. ressource_name = self.filename
  472. elif self.rawmode == 'one-dir':
  473. ressource_name = self.dirname
  474. else:
  475. raise (NotImlementedError)
  476. if cache_path == 'home':
  477. if sys.platform.startswith('win'):
  478. dirname = os.path.join(os.environ['APPDATA'], 'neo_rawio_cache')
  479. elif sys.platform.startswith('darwin'):
  480. dirname = '~/Library/Application Support/neo_rawio_cache'
  481. else:
  482. dirname = os.path.expanduser('~/.config/neo_rawio_cache')
  483. dirname = os.path.join(dirname, self.__class__.__name__)
  484. if not os.path.exists(dirname):
  485. os.makedirs(dirname)
  486. elif cache_path == 'same_as_resource':
  487. dirname = os.path.dirname(ressource_name)
  488. else:
  489. assert os.path.exists(cache_path), \
  490. 'cache_path do not exists use "home" or "same_as_file" to make this auto'
  491. # the hash of the ressource (dir of file) is done with filename+datetime
  492. # TODO make something more sofisticated when rawmode='one-dir' that use all filename and datetime
  493. d = dict(ressource_name=ressource_name, mtime=os.path.getmtime(ressource_name))
  494. hash = joblib.hash(d, hash_name='md5')
  495. # name is compund by the real_n,ame and the hash
  496. name = '{}_{}'.format(os.path.basename(ressource_name), hash)
  497. self.cache_filename = os.path.join(dirname, name)
  498. if os.path.exists(self.cache_filename):
  499. self.logger.warning('Use existing cache file {}'.format(self.cache_filename))
  500. self._cache = joblib.load(self.cache_filename)
  501. else:
  502. self.logger.warning('Create cache file {}'.format(self.cache_filename))
  503. self._cache = {}
  504. self.dump_cache()
  505. def add_in_cache(self, **kargs):
  506. assert self.use_cache
  507. self._cache.update(kargs)
  508. self.dump_cache()
  509. def dump_cache(self):
  510. assert self.use_cache
  511. joblib.dump(self._cache, self.cache_filename)
  512. ##################
  513. # Functions to be implement in IO below here
  514. def _parse_header(self):
  515. raise (NotImplementedError)
  516. # must call
  517. # self._generate_empty_annotations()
  518. def _source_name(self):
  519. raise (NotImplementedError)
  520. def _segment_t_start(self, block_index, seg_index):
  521. raise (NotImplementedError)
  522. def _segment_t_stop(self, block_index, seg_index):
  523. raise (NotImplementedError)
  524. ###
  525. # signal and channel zone
  526. def _get_signal_size(self, block_index, seg_index, channel_indexes):
  527. raise (NotImplementedError)
  528. def _get_signal_t_start(self, block_index, seg_index, channel_indexes):
  529. raise (NotImplementedError)
  530. def _get_analogsignal_chunk(self, block_index, seg_index, i_start, i_stop, channel_indexes):
  531. raise (NotImplementedError)
  532. ###
  533. # spiketrain and unit zone
  534. def _spike_count(self, block_index, seg_index, unit_index):
  535. raise (NotImplementedError)
  536. def _get_spike_timestamps(self, block_index, seg_index, unit_index, t_start, t_stop):
  537. raise (NotImplementedError)
  538. def _rescale_spike_timestamp(self, spike_timestamps, dtype):
  539. raise (NotImplementedError)
  540. ###
  541. # spike waveforms zone
  542. def _get_spike_raw_waveforms(self, block_index, seg_index, unit_index, t_start, t_stop):
  543. raise (NotImplementedError)
  544. ###
  545. # event and epoch zone
  546. def _event_count(self, block_index, seg_index, event_channel_index):
  547. raise (NotImplementedError)
  548. def _get_event_timestamps(self, block_index, seg_index, event_channel_index, t_start, t_stop):
  549. raise (NotImplementedError)
  550. def _rescale_event_timestamp(self, event_timestamps, dtype):
  551. raise (NotImplementedError)
  552. def _rescale_epoch_duration(self, raw_duration, dtype):
  553. raise (NotImplementedError)