neoNIXIO.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. # Ajayrama Kumaraswamy, 2016
  2. # Ginjang Project, LMU
  3. import nixio as nix
  4. import neo
  5. import quantities as qu
  6. import numpy as np
  7. qu2Val = lambda x: nix.Value(float(x))
  8. quUnitStr = lambda x: x.dimensionality.string
  9. #***********************************************************************************************************************
  10. def addAnalogSignal2Block(blk, analogSignal):
  11. '''
  12. Create a new data array in the block blk and add the data in analogSignal to it
  13. :param blk: nix.block
  14. :param analogSignal: neo.analogsignal
  15. :return: data, nix.data_array, the newly added data_array
  16. '''
  17. assert hasattr(analogSignal, 'name'), 'Analog signal has no name'
  18. data = blk.create_data_array(analogSignal.name, 'nix.regular_sampled', data=analogSignal.magnitude)
  19. data.unit = quUnitStr(analogSignal)
  20. data.label = analogSignal.name
  21. qu.set_default_units = 'SI'
  22. samplingPeriod = analogSignal.sampling_period.simplified
  23. t = data.append_sampled_dimension(float(samplingPeriod))
  24. t.label = 'time'
  25. t.unit = quUnitStr(samplingPeriod)
  26. t.offset = float(analogSignal.t_start.simplified)
  27. return data
  28. #***********************************************************************************************************************
  29. def dataArray2AnalogSignal(dataArray):
  30. '''
  31. Convert a nix data_array into a neo analogsignal
  32. :param dataArray: nix.data_array
  33. :return: neo.analogsignal
  34. '''
  35. assert len(dataArray.dimensions) == 1, 'Only one dimensional arrays are supported'
  36. dim = dataArray.dimensions[0]
  37. assert isinstance(dim, nix.pycore.SampledDimension), 'Only Sampled Dimensions' \
  38. 'are supported'
  39. t_start = qu.Quantity(dim.offset, units=dim.unit)
  40. samplingPeriod = qu.Quantity(dim.sampling_interval, units=dim.unit)
  41. analogSignal = neo.AnalogSignal(signal=np.array(dataArray[:]),
  42. units=dataArray.unit,
  43. sampling_period=samplingPeriod,
  44. t_start=t_start)
  45. analogSignal.name = dataArray.name
  46. return analogSignal
  47. #***********************************************************************************************************************
  48. def property2qu(property):
  49. '''
  50. Convert a nix property to a quantities Quantity
  51. :param property: nix.property
  52. :return: quantities.Quantity
  53. '''
  54. return qu.Quantity([v.value for v in property.values], units=property.unit)
  55. #***********************************************************************************************************************
  56. def addQuantity2section(sec, quant, name):
  57. '''
  58. Create new property in section sec and add the data in quantity.Quantitiy quant to it
  59. :param sec: nix.section
  60. :param quant: quantities.Quantity
  61. :param name: name of the property to add
  62. :return: p, nix.property, the property added.
  63. '''
  64. if quant.shape == ():
  65. p = sec.create_property(name, [qu2Val(quant)])
  66. #only 1D arrays
  67. elif len(quant.shape) == 1:
  68. #not an empty 1D array
  69. if quant.shape[0]:
  70. p = sec.create_property(name, [qu2Val(x) for x in quant])
  71. else:
  72. raise(ValueError('Quantity passed must be either scalar or 1 dimensional'))
  73. else:
  74. raise(ValueError('Quantity passed must be either scalar or 1 dimensional'))
  75. p.unit = quUnitStr(quant)
  76. return p
  77. #***********************************************************************************************************************
  78. def createPosDA(name, pos, blk):
  79. '''
  80. Create a data_array of type 'nix.positions' with the pos data in the block blk
  81. :param name: string, name of the data_array to create
  82. :param pos: iterable of floats, data to be added to the created data_array
  83. :param blk: nix.block, the block in which the data_array is to be created
  84. :return: positions, nix.data_array, the newly created data_array
  85. '''
  86. positions = blk.create_data_array(name, 'nix.positions', data=pos)
  87. positions.append_set_dimension()
  88. positions.append_set_dimension()
  89. return positions
  90. #***********************************************************************************************************************
  91. def createExtDA(name, ext, blk):
  92. '''
  93. Create a data_array of type 'nix.extents' with the pos data in the block blk
  94. :param name: string, name of the data_array to create
  95. :param ext: iterable of floats, data to be added to the created data_array
  96. :param blk: nix.block, the block in which the data_array is to be created
  97. :return: extents, nix.data_array, the newly created data_array
  98. '''
  99. extents = blk.create_data_array(name, 'nix.extents', data=ext)
  100. extents.append_set_dimension()
  101. extents.append_set_dimension()
  102. return extents
  103. #***********************************************************************************************************************
  104. def tag2AnalogSignal(tag, refInd):
  105. '''
  106. Create a neo.analogsignal from the snippet of data represented by a nix.tag and its reference at index refInd
  107. :param tag: nix.tag
  108. :param refInd: the index of the reference among those of the tag to use
  109. :return: neo.analogsignal with the snipped of reference data tagged by tag.
  110. '''
  111. ref = tag.references[refInd]
  112. dim = ref.dimensions[0]
  113. offset = dim.offset
  114. ts = dim.sampling_interval
  115. nSamples = ref[:].shape[0]
  116. startInd = max(0, int(np.floor((tag.position[0] - offset) / ts)))
  117. endInd = min(startInd + int(np.floor(tag.extent[0] / ts)) + 1, nSamples)
  118. trace = ref[startInd:endInd]
  119. analogSignal = neo.AnalogSignal(signal=trace,
  120. units=ref.unit,
  121. sampling_period=qu.Quantity(ts, units=dim.unit),
  122. t_start=qu.Quantity(offset + startInd * ts, units=dim.unit))
  123. analogSignal = analogSignal.reshape((analogSignal.shape[0],))
  124. # trace = tag.retrieve_data(refInd)[:]
  125. # tVec = tag.position[0] + np.linspace(0, tag.extent[0], trace.shape[0])
  126. return analogSignal
  127. #***********************************************************************************************************************
  128. def getTagPosExt(tag):
  129. position = tag.position[0] * qu.Quantity(1, units=tag.units[0])
  130. extent = tag.extent[0] * qu.Quantity(1, units=tag.units[0])
  131. return position, extent
  132. #***********************************************************************************************************************
  133. def multiTag2SpikeTrain(tag, tStart, tStop):
  134. '''
  135. Create a neo.spiketrain from nix.multitag
  136. :param tag: nix.multitag
  137. :param tStart: float, time of start of the spike train in units of the multitag
  138. :param tStop: float, time of stop of the spike train in units of the multitag
  139. :return: neo.spiketrain
  140. '''
  141. if len(tag.positions):
  142. sp = neo.SpikeTrain(times=tag.positions[:], t_start=tStart, t_stop=tStop, units=tag.units[0])
  143. else:
  144. sp = neo.SpikeTrain(times=[], t_start=tStart, t_stop=tStop, units=qu.s)
  145. return sp
  146. #***********************************************************************************************************************
  147. def addMultiTag(name, type, positions, blk, refs, metadata=None, extents=None):
  148. '''
  149. Add a multi_tag to one or more data_arrays
  150. :param name: string, name of the multi_tag
  151. :param type: string, type of the multi_tag
  152. :param positions: quantities.Quantity, positions of the multi_tag
  153. :param blk: nix.Block, the block in which the multi_tag is to be created
  154. :param refs: list, list of nix.data_array objects, to which the multi_tag refers
  155. :param metadata: nix.Section, to which the the multi_tag refers
  156. :param extents: nix.data_array, extents of the multi_tag
  157. :return: nix.multi_tag, the newly created multi_tag
  158. '''
  159. refUnits0 = refs[0].dimensions[0].unit
  160. for ref in refs:
  161. assert len(ref.dimensions) == 1, 'Only 1D refs are supported for now.'
  162. assert ref.dimensions[0].unit == refUnits0, 'refs must have same time units'
  163. positionsUnitsNormed = simpleFloat(positions / qu.Quantity(1, units=refUnits0))
  164. positionsDA = createPosDA('{}_DA'.format(name), positionsUnitsNormed, blk)
  165. tag = blk.create_multi_tag(name, type, positionsDA)
  166. tag.units = [str(refUnits0)]
  167. if extents is not None:
  168. tag.extents = extents
  169. for ref in refs:
  170. tag.references.append(ref)
  171. if metadata is not None:
  172. tag.metadata = metadata
  173. #***********************************************************************************************************************
  174. def addTag(name, type, position, blk, refs, metadata=None, extent=None):
  175. '''
  176. Add a tag to one or more data_arrays
  177. :param name: string, name of the tag
  178. :param type: string, type of the tag
  179. :param position: float, position of the tag
  180. :param blk: nix.Block, the block in which the multi_tag is to be created
  181. :param refs: list, list of nix.data_array objects, to which the multi_tag refers
  182. :param metadata: nix.Section, to which the the multi_tag refers
  183. :param extent: float, extent of the multi_tag
  184. :return: nix.tag, the newly created tag
  185. '''
  186. tag = blk.create_tag(name, type, [position])
  187. if extent is not None:
  188. tag.extent = [extent]
  189. for ref in refs:
  190. tag.references.append(ref)
  191. tag.units = [str(ref.dimensions[0].unit)]
  192. if metadata is not None:
  193. tag.metadata = metadata
  194. #***********************************************************************************************************************
  195. def simpleFloat(quant):
  196. '''
  197. Float or List of float(s) of simplified version of a quantity that can be
  198. effectively represented as a float or list of floats.
  199. :param quant: a quantity.Quantity or an iterable of quantity.Quantity objects
  200. :return: float or iterable of floats depending on the argument quant
  201. '''
  202. # one element quantity
  203. if quant.shape == ():
  204. return float(quant.simplified)
  205. # 1D quantity
  206. elif len(quant.shape) == 1:
  207. if quant.shape[0]:
  208. return quant.simplified.magnitude.tolist()
  209. else:
  210. return []
  211. # 2D quantity
  212. elif len(quant.shape) == 2:
  213. if quant.shape[0]:
  214. # 2D column quantity
  215. if quant.shape[1] == 1:
  216. return quant.simplified.magnitude[:, 0].tolist()
  217. # 2D row quantity
  218. if quant.shape[0] == 1:
  219. return quant.simplified.magnitude[0, :].tolist()
  220. else:
  221. raise (TypeError('simpleFloat only supports scalar, '
  222. '1D, 2D row and 2D column quantities'))
  223. else:
  224. return []
  225. else:
  226. raise(TypeError('simpleFloat only supports scalar, '
  227. '1D, 2D row and 2D column quantities'))
  228. #***********************************************************************************************************************