igorproio.py 5.4 KB

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