neomatlabio.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. # -*- coding: utf-8 -*-
  2. """
  3. Module for reading/writing Neo objects in MATLAB format (.mat) versions
  4. 5 to 7.2.
  5. This module is a bridge for MATLAB users who want to adopt the Neo object
  6. representation. The nomenclature is the same but using Matlab structs and cell
  7. arrays. With this module MATLAB users can use neo.io to read a format and
  8. convert it to .mat.
  9. Supported : Read/Write
  10. Author: sgarcia, Robert Pröpper
  11. """
  12. from datetime import datetime
  13. from distutils import version
  14. import re
  15. import numpy as np
  16. import quantities as pq
  17. # check scipy
  18. try:
  19. import scipy.io
  20. import scipy.version
  21. except ImportError as err:
  22. HAVE_SCIPY = False
  23. SCIPY_ERR = err
  24. else:
  25. if version.LooseVersion(scipy.version.version) < '0.12.0':
  26. HAVE_SCIPY = False
  27. SCIPY_ERR = ImportError("your scipy version is too old to support "
  28. + "MatlabIO, you need at least 0.12.0. "
  29. + "You have %s" % scipy.version.version)
  30. else:
  31. HAVE_SCIPY = True
  32. SCIPY_ERR = None
  33. from neo.io.baseio import BaseIO
  34. from neo.core import (Block, Segment, AnalogSignal, Event, Epoch, SpikeTrain,
  35. objectnames, class_by_name)
  36. classname_lower_to_upper = {}
  37. for k in objectnames:
  38. classname_lower_to_upper[k.lower()] = k
  39. class NeoMatlabIO(BaseIO):
  40. """
  41. Class for reading/writing Neo objects in MATLAB format (.mat) versions
  42. 5 to 7.2.
  43. This module is a bridge for MATLAB users who want to adopt the Neo object
  44. representation. The nomenclature is the same but using Matlab structs and
  45. cell arrays. With this module MATLAB users can use neo.io to read a format
  46. and convert it to .mat.
  47. Rules of conversion:
  48. * Neo classes are converted to MATLAB structs.
  49. e.g., a Block is a struct with attributes "name", "file_datetime", ...
  50. * Neo one_to_many relationships are cellarrays in MATLAB.
  51. e.g., ``seg.analogsignals[2]`` in Python Neo will be
  52. ``seg.analogsignals{3}`` in MATLAB.
  53. * Quantity attributes are represented by 2 fields in MATLAB.
  54. e.g., ``anasig.t_start = 1.5 * s`` in Python
  55. will be ``anasig.t_start = 1.5`` and ``anasig.t_start_unit = 's'``
  56. in MATLAB.
  57. * classes that inherit from Quantity (AnalogSignal, SpikeTrain, ...) in
  58. Python will have 2 fields (array and units) in the MATLAB struct.
  59. e.g.: ``AnalogSignal( [1., 2., 3.], 'V')`` in Python will be
  60. ``anasig.array = [1. 2. 3]`` and ``anasig.units = 'V'`` in MATLAB.
  61. 1 - **Scenario 1: create data in MATLAB and read them in Python**
  62. This MATLAB code generates a block::
  63. block = struct();
  64. block.segments = { };
  65. block.name = 'my block with matlab';
  66. for s = 1:3
  67. seg = struct();
  68. seg.name = strcat('segment ',num2str(s));
  69. seg.analogsignals = { };
  70. for a = 1:5
  71. anasig = struct();
  72. anasig.signal = rand(100,1);
  73. anasig.signal_units = 'mV';
  74. anasig.t_start = 0;
  75. anasig.t_start_units = 's';
  76. anasig.sampling_rate = 100;
  77. anasig.sampling_rate_units = 'Hz';
  78. seg.analogsignals{a} = anasig;
  79. end
  80. seg.spiketrains = { };
  81. for t = 1:7
  82. sptr = struct();
  83. sptr.times = rand(30,1)*10;
  84. sptr.times_units = 'ms';
  85. sptr.t_start = 0;
  86. sptr.t_start_units = 'ms';
  87. sptr.t_stop = 10;
  88. sptr.t_stop_units = 'ms';
  89. seg.spiketrains{t} = sptr;
  90. end
  91. event = struct();
  92. event.times = [0, 10, 30];
  93. event.times_units = 'ms';
  94. event.labels = ['trig0'; 'trig1'; 'trig2'];
  95. seg.events{1} = event;
  96. epoch = struct();
  97. epoch.times = [10, 20];
  98. epoch.times_units = 'ms';
  99. epoch.durations = [4, 10];
  100. epoch.durations_units = 'ms';
  101. epoch.labels = ['a0'; 'a1'];
  102. seg.epochs{1} = epoch;
  103. block.segments{s} = seg;
  104. end
  105. save 'myblock.mat' block -V7
  106. This code reads it in Python::
  107. import neo
  108. r = neo.io.NeoMatlabIO(filename='myblock.mat')
  109. bl = r.read_block()
  110. print bl.segments[1].analogsignals[2]
  111. print bl.segments[1].spiketrains[4]
  112. 2 - **Scenario 2: create data in Python and read them in MATLAB**
  113. This Python code generates the same block as in the previous scenario::
  114. import neo
  115. import quantities as pq
  116. from scipy import rand, array
  117. bl = neo.Block(name='my block with neo')
  118. for s in range(3):
  119. seg = neo.Segment(name='segment' + str(s))
  120. bl.segments.append(seg)
  121. for a in range(5):
  122. anasig = neo.AnalogSignal(rand(100)*pq.mV, t_start=0*pq.s,
  123. sampling_rate=100*pq.Hz)
  124. seg.analogsignals.append(anasig)
  125. for t in range(7):
  126. sptr = neo.SpikeTrain(rand(40)*pq.ms, t_start=0*pq.ms, t_stop=10*pq.ms)
  127. seg.spiketrains.append(sptr)
  128. ev = neo.Event([0, 10, 30]*pq.ms, labels=array(['trig0', 'trig1', 'trig2']))
  129. ep = neo.Epoch([10, 20]*pq.ms, durations=[4, 10]*pq.ms, labels=array(['a0', 'a1']))
  130. seg.events.append(ev)
  131. seg.epochs.append(ep)
  132. from neo.io.neomatlabio import NeoMatlabIO
  133. w = NeoMatlabIO(filename='myblock.mat')
  134. w.write_block(bl)
  135. This MATLAB code reads it::
  136. load 'myblock.mat'
  137. block.name
  138. block.segments{2}.analogsignals{3}.signal
  139. block.segments{2}.analogsignals{3}.signal_units
  140. block.segments{2}.analogsignals{3}.t_start
  141. block.segments{2}.analogsignals{3}.t_start_units
  142. 3 - **Scenario 3: conversion**
  143. This Python code converts a Spike2 file to MATLAB::
  144. from neo import Block
  145. from neo.io import Spike2IO, NeoMatlabIO
  146. r = Spike2IO(filename='spike2.smr')
  147. w = NeoMatlabIO(filename='convertedfile.mat')
  148. blocks = r.read()
  149. w.write(blocks[0])
  150. """
  151. is_readable = True
  152. is_writable = True
  153. supported_objects = [Block, Segment, AnalogSignal, Epoch, Event, SpikeTrain]
  154. readable_objects = [Block]
  155. writeable_objects = [Block]
  156. has_header = False
  157. is_streameable = False
  158. read_params = {Block: []}
  159. write_params = {Block: []}
  160. name = 'neomatlab'
  161. extensions = ['mat']
  162. mode = 'file'
  163. def __init__(self, filename=None):
  164. """
  165. This class read/write neo objects in matlab 5 to 7.2 format.
  166. Arguments:
  167. filename : the filename to read
  168. """
  169. if not HAVE_SCIPY:
  170. raise SCIPY_ERR
  171. BaseIO.__init__(self)
  172. self.filename = filename
  173. def read_block(self, lazy=False):
  174. """
  175. Arguments:
  176. """
  177. assert not lazy, 'Do not support lazy'
  178. d = scipy.io.loadmat(self.filename, struct_as_record=False,
  179. squeeze_me=True, mat_dtype=True)
  180. if 'block' not in d:
  181. self.logger.exception('No block in ' + self.filename)
  182. return None
  183. bl_struct = d['block']
  184. bl = self.create_ob_from_struct(
  185. bl_struct, 'Block')
  186. bl.create_many_to_one_relationship()
  187. return bl
  188. def write_block(self, bl, **kargs):
  189. """
  190. Arguments:
  191. bl: the block to b saved
  192. """
  193. bl_struct = self.create_struct_from_obj(bl)
  194. for seg in bl.segments:
  195. seg_struct = self.create_struct_from_obj(seg)
  196. bl_struct['segments'].append(seg_struct)
  197. for anasig in seg.analogsignals:
  198. anasig_struct = self.create_struct_from_obj(anasig)
  199. seg_struct['analogsignals'].append(anasig_struct)
  200. for ea in seg.events:
  201. ea_struct = self.create_struct_from_obj(ea)
  202. seg_struct['events'].append(ea_struct)
  203. for ea in seg.epochs:
  204. ea_struct = self.create_struct_from_obj(ea)
  205. seg_struct['epochs'].append(ea_struct)
  206. for sptr in seg.spiketrains:
  207. sptr_struct = self.create_struct_from_obj(sptr)
  208. seg_struct['spiketrains'].append(sptr_struct)
  209. scipy.io.savemat(self.filename, {'block': bl_struct}, oned_as='row')
  210. def create_struct_from_obj(self, ob):
  211. struct = {}
  212. # relationship
  213. for childname in getattr(ob, '_single_child_containers', []):
  214. supported_containers = [subob.__name__.lower() + 's' for subob in
  215. self.supported_objects]
  216. if childname in supported_containers:
  217. struct[childname] = []
  218. # attributes
  219. for i, attr in enumerate(ob._all_attrs):
  220. attrname, attrtype = attr[0], attr[1]
  221. # ~ if attrname =='':
  222. # ~ struct['array'] = ob.magnitude
  223. # ~ struct['units'] = ob.dimensionality.string
  224. # ~ continue
  225. if (hasattr(ob, '_quantity_attr') and ob._quantity_attr == attrname):
  226. struct[attrname] = ob.magnitude
  227. struct[attrname + '_units'] = ob.dimensionality.string
  228. continue
  229. if not (attrname in ob.annotations or hasattr(ob, attrname)):
  230. continue
  231. if getattr(ob, attrname) is None:
  232. continue
  233. if attrtype == pq.Quantity:
  234. # ndim = attr[2]
  235. struct[attrname] = getattr(ob, attrname).magnitude
  236. struct[attrname + '_units'] = getattr(
  237. ob, attrname).dimensionality.string
  238. elif attrtype == datetime:
  239. struct[attrname] = str(getattr(ob, attrname))
  240. else:
  241. struct[attrname] = getattr(ob, attrname)
  242. return struct
  243. def create_ob_from_struct(self, struct, classname):
  244. cl = class_by_name[classname]
  245. # check if hinerits Quantity
  246. # ~ is_quantity = False
  247. # ~ for attr in cl._necessary_attrs:
  248. # ~ if attr[0] == '' and attr[1] == pq.Quantity:
  249. # ~ is_quantity = True
  250. # ~ break
  251. # ~ is_quantiy = hasattr(cl, '_quantity_attr')
  252. # ~ if is_quantity:
  253. if hasattr(cl, '_quantity_attr'):
  254. quantity_attr = cl._quantity_attr
  255. arr = getattr(struct, quantity_attr)
  256. # ~ data_complement = dict(units=str(struct.units))
  257. data_complement = dict(units=str(
  258. getattr(struct, quantity_attr + '_units')))
  259. if "sampling_rate" in (at[0] for at in cl._necessary_attrs):
  260. # put fake value for now, put correct value later
  261. data_complement["sampling_rate"] = 0 * pq.kHz
  262. try:
  263. len(arr)
  264. except TypeError:
  265. # strange scipy.io behavior: if len is 1 we get a float
  266. arr = np.array(arr)
  267. arr = arr.reshape((-1,)) # new view with one dimension
  268. if "t_stop" in (at[0] for at in cl._necessary_attrs):
  269. if len(arr) > 0:
  270. data_complement["t_stop"] = arr.max()
  271. else:
  272. data_complement["t_stop"] = 0.0
  273. if "t_start" in (at[0] for at in cl._necessary_attrs):
  274. if len(arr) > 0:
  275. data_complement["t_start"] = arr.min()
  276. else:
  277. data_complement["t_start"] = 0.0
  278. ob = cl(arr, **data_complement)
  279. else:
  280. ob = cl()
  281. for attrname in struct._fieldnames:
  282. # check children
  283. if attrname in getattr(ob, '_single_child_containers', []):
  284. child_struct = getattr(struct, attrname)
  285. try:
  286. # try must only surround len() or other errors are captured
  287. child_len = len(child_struct)
  288. except TypeError:
  289. # strange scipy.io behavior: if len is 1 there is no len()
  290. child = self.create_ob_from_struct(
  291. child_struct,
  292. classname_lower_to_upper[attrname[:-1]])
  293. getattr(ob, attrname.lower()).append(child)
  294. else:
  295. for c in range(child_len):
  296. child = self.create_ob_from_struct(
  297. child_struct[c],
  298. classname_lower_to_upper[attrname[:-1]])
  299. getattr(ob, attrname.lower()).append(child)
  300. continue
  301. # attributes
  302. if attrname.endswith('_units') or attrname == 'units':
  303. # linked with another field
  304. continue
  305. if (hasattr(cl, '_quantity_attr') and cl._quantity_attr == attrname):
  306. continue
  307. item = getattr(struct, attrname)
  308. attributes = cl._necessary_attrs + cl._recommended_attrs
  309. dict_attributes = dict([(a[0], a[1:]) for a in attributes])
  310. if attrname in dict_attributes:
  311. attrtype = dict_attributes[attrname][0]
  312. if attrtype == datetime:
  313. m = r'(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+).(\d+)'
  314. r = re.findall(m, str(item))
  315. if len(r) == 1:
  316. item = datetime(*[int(e) for e in r[0]])
  317. else:
  318. item = None
  319. elif attrtype == np.ndarray:
  320. dt = dict_attributes[attrname][2]
  321. item = item.astype(dt)
  322. elif attrtype == pq.Quantity:
  323. ndim = dict_attributes[attrname][1]
  324. units = str(getattr(struct, attrname + '_units'))
  325. if ndim == 0:
  326. item = pq.Quantity(item, units)
  327. else:
  328. item = pq.Quantity(item, units)
  329. else:
  330. item = attrtype(item)
  331. setattr(ob, attrname, item)
  332. return ob