neomatlabio.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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, 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, cascade=True, lazy=False,):
  173. """
  174. Arguments:
  175. """
  176. d = scipy.io.loadmat(self.filename, struct_as_record=False,
  177. squeeze_me=True, mat_dtype=True)
  178. if not 'block' in d:
  179. self.logger.exception('No block in ' + self.filename)
  180. return None
  181. bl_struct = d['block']
  182. bl = self.create_ob_from_struct(
  183. bl_struct, 'Block', cascade=cascade, lazy=lazy)
  184. bl.create_many_to_one_relationship()
  185. return bl
  186. def write_block(self, bl, **kargs):
  187. """
  188. Arguments:
  189. bl: the block to b saved
  190. """
  191. bl_struct = self.create_struct_from_obj(bl)
  192. for seg in bl.segments:
  193. seg_struct = self.create_struct_from_obj(seg)
  194. bl_struct['segments'].append(seg_struct)
  195. for anasig in seg.analogsignals:
  196. anasig_struct = self.create_struct_from_obj(anasig)
  197. seg_struct['analogsignals'].append(anasig_struct)
  198. for ea in seg.events:
  199. ea_struct = self.create_struct_from_obj(ea)
  200. seg_struct['events'].append(ea_struct)
  201. for ea in seg.epochs:
  202. ea_struct = self.create_struct_from_obj(ea)
  203. seg_struct['epochs'].append(ea_struct)
  204. for sptr in seg.spiketrains:
  205. sptr_struct = self.create_struct_from_obj(sptr)
  206. seg_struct['spiketrains'].append(sptr_struct)
  207. scipy.io.savemat(self.filename, {'block': bl_struct}, oned_as='row')
  208. def create_struct_from_obj(self, ob):
  209. struct = {}
  210. # relationship
  211. for childname in getattr(ob, '_single_child_containers', []):
  212. supported_containers = [subob.__name__.lower() + 's' for subob in
  213. self.supported_objects]
  214. if childname in supported_containers:
  215. struct[childname] = []
  216. # attributes
  217. for i, attr in enumerate(ob._all_attrs):
  218. attrname, attrtype = attr[0], attr[1]
  219. #~ if attrname =='':
  220. #~ struct['array'] = ob.magnitude
  221. #~ struct['units'] = ob.dimensionality.string
  222. #~ continue
  223. if (hasattr(ob, '_quantity_attr') and
  224. ob._quantity_attr == attrname):
  225. struct[attrname] = ob.magnitude
  226. struct[attrname+'_units'] = ob.dimensionality.string
  227. continue
  228. if not(attrname in ob.annotations or hasattr(ob, attrname)):
  229. continue
  230. if getattr(ob, attrname) is None:
  231. continue
  232. if attrtype == pq.Quantity:
  233. #ndim = attr[2]
  234. struct[attrname] = getattr(ob, attrname).magnitude
  235. struct[attrname + '_units'] = getattr(
  236. ob, attrname).dimensionality.string
  237. elif attrtype == datetime:
  238. struct[attrname] = str(getattr(ob, attrname))
  239. else:
  240. struct[attrname] = getattr(ob, attrname)
  241. return struct
  242. def create_ob_from_struct(self, struct, classname,
  243. cascade=True, lazy=False):
  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. if "t_stop" in (at[0] for at in cl._necessary_attrs):
  263. if len(arr) > 0:
  264. data_complement["t_stop"] = arr.max()
  265. else:
  266. data_complement["t_stop"] = 0.0
  267. if "t_start" in (at[0] for at in cl._necessary_attrs):
  268. if len(arr) > 0:
  269. data_complement["t_start"] = arr.min()
  270. else:
  271. data_complement["t_start"] = 0.0
  272. if lazy:
  273. ob = cl([], **data_complement)
  274. ob.lazy_shape = arr.shape
  275. else:
  276. ob = cl(arr, **data_complement)
  277. else:
  278. ob = cl()
  279. for attrname in struct._fieldnames:
  280. # check children
  281. if attrname in getattr(ob, '_single_child_containers', []):
  282. try:
  283. for c in range(len(getattr(struct, attrname))):
  284. if cascade:
  285. child = self.create_ob_from_struct(
  286. getattr(struct, attrname)[c],
  287. classname_lower_to_upper[attrname[:-1]],
  288. cascade=cascade, lazy=lazy)
  289. getattr(ob, attrname.lower()).append(child)
  290. except TypeError:
  291. # strange scipy.io behavior: if len is 1 there is no len()
  292. if cascade:
  293. child = self.create_ob_from_struct(
  294. getattr(struct, attrname),
  295. classname_lower_to_upper[attrname[:-1]],
  296. cascade=cascade, lazy=lazy)
  297. getattr(ob, attrname.lower()).append(child)
  298. continue
  299. # attributes
  300. if attrname.endswith('_units') or attrname == 'units':
  301. # linked with another field
  302. continue
  303. if (hasattr(cl, '_quantity_attr') and
  304. cl._quantity_attr == attrname):
  305. continue
  306. item = getattr(struct, attrname)
  307. attributes = cl._necessary_attrs + cl._recommended_attrs
  308. dict_attributes = dict([(a[0], a[1:]) for a in attributes])
  309. if attrname in dict_attributes:
  310. attrtype = dict_attributes[attrname][0]
  311. if attrtype == datetime:
  312. m = '(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+).(\d+)'
  313. r = re.findall(m, str(item))
  314. if len(r) == 1:
  315. item = datetime(*[int(e) for e in r[0]])
  316. else:
  317. item = None
  318. elif attrtype == np.ndarray:
  319. dt = dict_attributes[attrname][2]
  320. if lazy:
  321. item = np.array([], dtype=dt)
  322. ob.lazy_shape = item.shape
  323. else:
  324. item = item.astype(dt)
  325. elif attrtype == pq.Quantity:
  326. ndim = dict_attributes[attrname][1]
  327. units = str(getattr(struct, attrname+'_units'))
  328. if ndim == 0:
  329. item = pq.Quantity(item, units)
  330. else:
  331. if lazy:
  332. item = pq.Quantity([], units)
  333. item.lazy_shape = item.shape
  334. else:
  335. item = pq.Quantity(item, units)
  336. else:
  337. item = attrtype(item)
  338. setattr(ob, attrname, item)
  339. return ob