pyrelacs.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. from os import path
  2. try:
  3. from itertools import izip
  4. except:
  5. izip = zip
  6. import types
  7. from numpy import array, arange, NaN, fromfile, float32, asarray, squeeze, isnan, fromstring
  8. from numpy.core.records import fromarrays
  9. # import nixio as nix
  10. import re
  11. import warnings
  12. identifiers = {
  13. 'stimspikes1.dat': lambda info: ('RePro' in info[-1] and info[-1]['RePro'] == 'FileStimulus'),
  14. 'samallspikes1.dat': lambda info: ('RePro' in info[-1] and info[-1]['RePro'] == 'SAM'),
  15. }
  16. def isfloat(value):
  17. try:
  18. float(value)
  19. return True
  20. except ValueError:
  21. return False
  22. def info_filter(iter, filterfunc):
  23. for info, key, dat in iter:
  24. if filterfunc(info):
  25. yield info, key, dat
  26. def iload_io_pairs(basedir, spikefile, traces, filterfunc=None):
  27. """
  28. Iterator that returns blocks of spike traces and spike times from the base directory basedir (e.g. 2014-06-06-aa)
  29. and the spiketime file (e.g. stimspikes1.dat). A filter function can filter out unwanted blocks. It gets the info
  30. (see iload and iload trace_trials) from all traces and spike times and has to return True is the block is wanted
  31. and False otherwise.
  32. :param basedir: basis directory of the recordings (e.g. 2014-06-06-aa)
  33. :param spikefile: spikefile (e.g. stimspikes1.dat)
  34. :param traces: trace numbers as a list (e.g. [1,2])
  35. :param filterfunc: function that gets the infos from all traces and spike times and indicates whether the block is wanted or not
  36. """
  37. if filterfunc is None: filterfunc = lambda inp: True
  38. if type(traces) is not types.ListType:
  39. traces = [traces]
  40. assert spikefile in identifiers, """iload_io_pairs does not know how to identify trials in stimuli.dat which
  41. correspond to trials in {0}. Please update pyRELACS.DataLoader.identifiers
  42. accordingly""".format(spikefile)
  43. iterators = [info_filter(iload_trace_trials(basedir, tn), identifiers[spikefile]) for tn in traces] \
  44. + [iload_spike_blocks(basedir + '/' + spikefile)]
  45. for stuff in izip(*iterators):
  46. info, key, dat = zip(*stuff)
  47. if filterfunc(*info):
  48. yield info, key, dat
  49. def iload_spike_blocks(filename):
  50. """
  51. Loades spike times from filename and merges trials with incremental trial numbers into one block.
  52. Spike times are assumed to be in seconds and are converted into ms.
  53. """
  54. current_trial = -1
  55. ret_dat = []
  56. old_info = old_key = None
  57. for info, key, dat in iload(filename):
  58. if 'trial' in info[-1]:
  59. if int(info[-1]['trial']) != current_trial + 1:
  60. yield old_info[:-1], key, ret_dat
  61. ret_dat = []
  62. current_trial = int(info[-1]['trial'])
  63. if not any(isnan(dat)):
  64. ret_dat.append(squeeze(dat) / 1000.)
  65. else:
  66. ret_dat.append(array([]))
  67. old_info, old_key = info, key
  68. else:
  69. if len(ret_dat) > 0:
  70. yield old_info[:-1], old_key, ret_dat
  71. ret_dat = []
  72. yield info, key, dat
  73. else:
  74. if len(ret_dat) > 0:
  75. yield old_info[:-1], old_key, ret_dat
  76. def iload_trace_trials(basedir, trace_no=1, before=0.0, after=0.0):
  77. """
  78. returns:
  79. info : metadata from stimuli.dat
  80. key : key from stimuli.dat
  81. data : the data of the specified trace of all trials
  82. """
  83. x = fromfile('%s/trace-%i.raw' % (basedir, trace_no), float32)
  84. p = re.compile('([-+]?\d*\.\d+|\d+)\s*(\w+)')
  85. for info, key, dat in iload('%s/stimuli.dat' % (basedir,)):
  86. X = []
  87. val, unit = p.match(info[-1]['duration']).groups()
  88. val = float(val)
  89. if unit == 'ms':
  90. val *= 0.001
  91. duration_index = key[2].index('duration')
  92. # if 'RePro' in info[1] and info[1]['RePro'] == 'FileStimulus':
  93. # embed()
  94. # exit()
  95. sval, sunit = p.match(info[0]['sample interval%i' % trace_no]).groups()
  96. sval = float(sval)
  97. if sunit == 'ms':
  98. sval *= 0.001
  99. l = int(before / sval)
  100. r = int((val + after) / sval)
  101. if dat.shape == (1, 1) and dat[0, 0] == 0:
  102. warnings.warn("iload_trace_trials: Encountered incomplete '-0' trial.")
  103. yield info, key, array([])
  104. continue
  105. for col, duration in zip(asarray([e[trace_no - 1] for e in dat], dtype=int),
  106. asarray([e[duration_index] for e in dat],
  107. dtype=float32)): # dat[:,trace_no-1].astype(int):
  108. tmp = x[col - l:col + r]
  109. if duration < 0.001: # if the duration is less than 1ms
  110. warnings.warn(
  111. "iload_trace_trials: Skipping one trial because its duration is <1ms and therefore it is probably rubbish")
  112. continue
  113. if len(X) > 0 and len(tmp) != len(X[0]):
  114. warnings.warn("iload_trace_trials: Setting one trial to NaN because it appears to be incomplete!")
  115. X.append(NaN * X[0])
  116. else:
  117. X.append(tmp)
  118. yield info, key, asarray(X)
  119. def iload_traces(basedir, repro='', before=0.0, after=0.0):
  120. """
  121. returns:
  122. info : metadata from stimuli.dat
  123. key : key from stimuli.dat
  124. time : an array for the time axis
  125. data : the data of all traces of a single trial
  126. """
  127. # open traces files:
  128. sf = get_raw_traces(basedir)
  129. for info, key, dat in iload('%s/stimuli.dat' % (basedir,)):
  130. if len(repro) > 0 and repro != info[1]['RePro']:
  131. continue
  132. start_idx, end_idx = get_trace_indices(info, before, after)
  133. deltat = get_sampling_interval(info)
  134. time = arange(0.0, start_idx + end_idx) * deltat - before
  135. duration_index = key[2].index('duration')
  136. if dat.shape == (1, 1) and dat[0, 0] == 0:
  137. warnings.warn("iload_traces: Encountered incomplete '-0' trial.")
  138. yield info, key, array([])
  139. continue
  140. for d in dat:
  141. duration = d[duration_index]
  142. if duration < 0.001: # if the duration is less than 1ms
  143. warnings.warn(
  144. "iload_traces: Skipping one trial because its duration is <1ms and therefore it is probably rubbish")
  145. continue
  146. x = []
  147. for trace in xrange(len(sf)):
  148. col = int(d[trace])
  149. from_idx = col - start_idx
  150. indexes_to_read = (start_idx + end_idx)
  151. trace_file = sf[trace]
  152. tmp = read_from_trace_file(trace_file, from_idx, indexes_to_read)
  153. if len(x) > 0 and len(tmp) != len(x[0]):
  154. warnings.warn("iload_traces: Setting one trial to NaN because it appears to be incomplete!")
  155. x.append(NaN * x[0])
  156. else:
  157. x.append(tmp)
  158. yield info, key, time, asarray(x)
  159. def get_sampling_interval(info):
  160. p = re.compile('(\d+[e][+]\d*|[-+]?\d*\.\d+|\d+)\s*(\w+)')
  161. deltat, unit = p.match(info[0]['sample interval1']).groups()
  162. deltat = float(deltat)
  163. if unit == 'ms':
  164. deltat *= 0.001
  165. return deltat
  166. def read_from_trace_file(trace_file, from_idx, to_idx):
  167. trace_file.seek(int(from_idx) * 4)
  168. buffer = trace_file.read(int(to_idx) * 4)
  169. tmp = fromstring(buffer, float32)
  170. return tmp
  171. def get_trace_indices(repro_info, before, after):
  172. p = re.compile('(\d+[e][+]\d*|[-+]?\d*\.\d+|\d+)\s*(\w+)')
  173. val, unit = p.match(repro_info[-1]['duration']).groups()
  174. val = float(val)
  175. if unit == 'ms':
  176. val *= 0.001
  177. sval, sunit = p.match(repro_info[0]['sample interval%i' % 1]).groups()
  178. sval = float(sval)
  179. if sunit == 'ms':
  180. sval *= 0.001
  181. l = int(before / sval)
  182. r = int((val + after) / sval)
  183. return l, r
  184. def get_raw_traces(basedir):
  185. sf = []
  186. for trace in xrange(1, 1000000):
  187. if path.isfile('%s/trace-%i.raw' % (basedir, trace)):
  188. sf.append(get_raw_trace(basedir, trace))
  189. else:
  190. break
  191. return sf
  192. def get_raw_trace(basedir, trace):
  193. return open('%s/trace-%i.raw' % (basedir, trace), 'rb')
  194. def get_available_traces(basedir):
  195. stimulus_file = "{}/stimuli.dat".format(basedir)
  196. traces_info = []
  197. info_pattern = re.compile('#\s+([\w\s]*):\s+([\w+-.]*)')
  198. key_pattern = re.compile('(\w*)(\d+)')
  199. with open(stimulus_file, 'r') as fid:
  200. read = False
  201. for line in fid:
  202. if "analog input traces" in line:
  203. read = True
  204. continue
  205. if "event lists" in line:
  206. break
  207. if read:
  208. key, value = map(lambda s: s.strip(), info_pattern.match(line).groups())
  209. key = key.replace(" ", "_")
  210. key_id, key_idx = key_pattern.match(key).groups()
  211. key_idx = int(key_idx) - 1
  212. if key_id == "identifier":
  213. traces_info.append({key_id: value})
  214. else:
  215. traces_info[key_idx].update({key_id: value})
  216. return traces_info
  217. def iload(filename):
  218. meta_data = []
  219. new_meta_data = []
  220. key = []
  221. within_key = within_meta_block = within_data_block = False
  222. currkey = None
  223. data = []
  224. with open(filename, 'r') as fid:
  225. for line in fid:
  226. line = line.rstrip().lstrip()
  227. if within_data_block and (line.startswith('#') or not line):
  228. within_data_block = False
  229. yield list(meta_data), tuple(key), array(data)
  230. data = []
  231. # Key parsing
  232. if line.startswith('#Key'):
  233. key = []
  234. within_key = True
  235. continue
  236. if within_key:
  237. if not line.startswith('#'):
  238. within_key = False
  239. if not line and key[-1][0] == '1':
  240. within_data_block = True
  241. n = len(new_meta_data)
  242. meta_data[-n:] = new_meta_data
  243. new_meta_data = []
  244. currkey = None
  245. continue
  246. else:
  247. key.append(tuple([e.strip() for e in line[1:].split(" ") if len(e.strip()) > 0]))
  248. continue
  249. # fast forward to first data point or meta data
  250. if not line:
  251. within_key = within_meta_block = False
  252. currkey = None
  253. continue
  254. # meta data blocks
  255. elif line.startswith('#'): # cannot be a key anymore
  256. if not within_meta_block:
  257. within_meta_block = True
  258. new_meta_data.append({})
  259. if ':' in line:
  260. tmp = [e.rstrip().lstrip() for e in line[1:].split(':')]
  261. elif '=' in line:
  262. tmp = [e.rstrip().lstrip() for e in line[1:].split('=')]
  263. else:
  264. currkey = line[1:].rstrip().lstrip()
  265. new_meta_data[-1][currkey] = {}
  266. continue
  267. if currkey is None:
  268. new_meta_data[-1][tmp[0]] = tmp[1]
  269. else:
  270. new_meta_data[-1][currkey][tmp[0]] = tmp[1]
  271. else:
  272. if not within_data_block:
  273. within_data_block = True
  274. n = len(new_meta_data)
  275. meta_data[-n:] = new_meta_data
  276. new_meta_data = []
  277. currkey = None
  278. within_key = within_meta_block = False
  279. data.append([float(e) if (e != '-0' and isfloat(e)) else NaN for e in line.split()])
  280. else: # if for loop is finished, return the data we have so far
  281. if within_data_block and len(data) > 0:
  282. yield list(meta_data), tuple(key), array(data)
  283. def recload(filename):
  284. for meta, key, dat in iload(filename):
  285. yield meta, fromarrays(dat.T, names=key[0])
  286. def load(filename):
  287. """
  288. Loads a data file saved by relacs. Returns a tuple of dictionaries
  289. containing the data and the header information
  290. :param filename: Filename of the data file.
  291. :type filename: string
  292. :returns: a tuple of dictionaries containing the head information and the data.
  293. :rtype: tuple
  294. """
  295. with open(filename, 'r') as fid:
  296. L = [l.lstrip().rstrip() for l in fid.readlines()]
  297. ret = []
  298. dat = {}
  299. X = []
  300. keyon = False
  301. currkey = None
  302. for l in L:
  303. # if empty line and we have data recorded
  304. if (not l or l.startswith('#')) and len(X) > 0:
  305. keyon = False
  306. currkey = None
  307. dat['data'] = array(X)
  308. ret.append(dat)
  309. X = []
  310. dat = {}
  311. if '---' in l:
  312. continue
  313. if l.startswith('#'):
  314. if ":" in l:
  315. tmp = [e.rstrip().lstrip() for e in l[1:].split(':')]
  316. if currkey is None:
  317. dat[tmp[0]] = tmp[1]
  318. else:
  319. dat[currkey][tmp[0]] = tmp[1]
  320. elif "=" in l:
  321. tmp = [e.rstrip().lstrip() for e in l[1:].split('=')]
  322. if currkey is None:
  323. dat[tmp[0]] = tmp[1]
  324. else:
  325. dat[currkey][tmp[0]] = tmp[1]
  326. elif l[1:].lower().startswith('key'):
  327. dat['key'] = []
  328. keyon = True
  329. elif keyon:
  330. dat['key'].append(tuple([e.lstrip().rstrip() for e in l[1:].split()]))
  331. else:
  332. currkey = l[1:].rstrip().lstrip()
  333. dat[currkey] = {}
  334. elif l: # if l != ''
  335. keyon = False
  336. currkey = None
  337. X.append([float(e) for e in l.split()])
  338. if len(X) > 0:
  339. dat['data'] = array(X)
  340. else:
  341. dat['data'] = []
  342. ret.append(dat)
  343. return tuple(ret)