igorproio.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. # -*- coding: utf-8 -*-
  2. """
  3. Class for reading data created by IGOR Pro
  4. (WaveMetrics, Inc., Portland, OR, USA)
  5. Depends on: igor (https://pypi.python.org/pypi/igor/)
  6. Supported: Read
  7. Author: Andrew Davison
  8. Also contributing: Rick Gerkin
  9. """
  10. from __future__ import absolute_import
  11. from warnings import warn
  12. import numpy as np
  13. import quantities as pq
  14. from neo.io.baseio import BaseIO
  15. from neo.core import Block, Segment, AnalogSignal
  16. try:
  17. import igor.binarywave as bw
  18. import igor.packed as pxp
  19. HAVE_IGOR = True
  20. except ImportError:
  21. HAVE_IGOR = False
  22. class IgorIO(BaseIO):
  23. """
  24. Class for reading Igor Binary Waves (.ibw) written by WaveMetrics’
  25. IGOR Pro software.
  26. Support for Packed Experiment (.pxp) files is planned.
  27. It requires the `igor` Python package by W. Trevor King.
  28. Usage:
  29. >>> from neo import io
  30. >>> r = io.IgorIO(filename='...ibw')
  31. """
  32. is_readable = True # This class can only read data
  33. is_writable = False # write is not supported
  34. supported_objects = [Block, Segment, AnalogSignal]
  35. readable_objects = [Block, Segment , AnalogSignal]
  36. writeable_objects = []
  37. has_header = False
  38. is_streameable = False
  39. name = 'igorpro'
  40. extensions = ['ibw', 'pxp']
  41. mode = 'file'
  42. def __init__(self, filename=None, parse_notes=None) :
  43. """
  44. Arguments:
  45. filename: the filename
  46. parse_notes: (optional) A function which will parse the 'notes'
  47. field in the file header and return a dictionary which will be
  48. added to the object annotations.
  49. """
  50. BaseIO.__init__(self)
  51. assert any([filename.endswith('.%s' % x) for x in self.extensions]), \
  52. "Only the following extensions are supported: %s" % self.extensions
  53. self.filename = filename
  54. self.extension = filename.split('.')[-1]
  55. self.parse_notes = parse_notes
  56. def read_block(self, lazy=False, cascade=True):
  57. block = Block(file_origin=self.filename)
  58. if cascade:
  59. block.segments.append(self.read_segment(lazy=lazy, cascade=cascade))
  60. block.segments[-1].block = block
  61. return block
  62. def read_segment(self, lazy=False, cascade=True):
  63. segment = Segment(file_origin=self.filename)
  64. if cascade:
  65. segment.analogsignals.append(
  66. self.read_analogsignal(lazy=lazy, cascade=cascade))
  67. segment.analogsignals[-1].segment = segment
  68. return segment
  69. def read_analogsignal(self, path=None, lazy=False, cascade=True):
  70. if not HAVE_IGOR:
  71. raise Exception(("`igor` package not installed. "
  72. "Try `pip install igor`"))
  73. if self.extension == 'ibw':
  74. data = bw.load(self.filename)
  75. version = data['version']
  76. if version > 5:
  77. raise IOError(("Igor binary wave file format version {0} "
  78. "is not supported.".format(version)))
  79. elif self.extension == 'pxp':
  80. assert type(path) is str, \
  81. "A colon-separated Igor-style path must be provided."
  82. _,filesystem = pxp.load(self.filename)
  83. path = path.split(':')
  84. location = filesystem['root']
  85. for element in path:
  86. if element != 'root':
  87. location = location[element.encode('utf8')]
  88. data = location.wave
  89. content = data['wave']
  90. if "padding" in content:
  91. assert content['padding'].size == 0, \
  92. "Cannot handle non-empty padding"
  93. if lazy:
  94. # not really lazy, since the `igor` module loads the data anyway
  95. signal = np.array((), dtype=content['wData'].dtype)
  96. else:
  97. signal = content['wData']
  98. note = content['note']
  99. header = content['wave_header']
  100. name = header['bname']
  101. units = "".join([x.decode() for x in header['dataUnits']])
  102. try:
  103. time_units = "".join([x.decode() for x in header['xUnits']])
  104. assert len(time_units)
  105. except:
  106. time_units = "s"
  107. try:
  108. t_start = pq.Quantity(header['hsB'], time_units)
  109. except KeyError:
  110. t_start = pq.Quantity(header['sfB'][0], time_units)
  111. try:
  112. sampling_period = pq.Quantity(header['hsA'], time_units)
  113. except:
  114. sampling_period = pq.Quantity(header['sfA'][0], time_units)
  115. if self.parse_notes:
  116. try:
  117. annotations = self.parse_notes(note)
  118. except ValueError:
  119. warn("Couldn't parse notes field.")
  120. annotations = {'note': note}
  121. else:
  122. annotations = {'note': note}
  123. signal = AnalogSignal(signal, units=units, copy=False, t_start=t_start,
  124. sampling_period=sampling_period, name=name,
  125. file_origin=self.filename, **annotations)
  126. if lazy:
  127. signal.lazy_shape = content['wData'].shape
  128. return signal
  129. # the following function is to handle the annotations in the
  130. # Igor data files from the Blue Brain Project NMC Portal
  131. def key_value_string_parser(itemsep=";", kvsep=":"):
  132. """
  133. Parses a string into a dict.
  134. Arguments:
  135. itemsep - character which separates items
  136. kvsep - character which separates the key and value within an item
  137. Returns:
  138. a function which takes the string to be parsed as the sole argument
  139. and returns a dict.
  140. Example:
  141. >>> parse = key_value_string_parser(itemsep=";", kvsep=":")
  142. >>> parse("a:2;b:3")
  143. {'a': 2, 'b': 3}
  144. """
  145. def parser(s):
  146. items = s.split(itemsep)
  147. return dict(item.split(kvsep, 1) for item in items if item)
  148. return parser