neomatlabio.py 15 KB

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