igorproio.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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)
  25. or Packed Experiment (.pxp) files written by WaveMetrics’
  26. IGOR Pro software.
  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):
  57. assert not lazy, 'Do not support lazy'
  58. block = Block(file_origin=self.filename)
  59. block.segments.append(self.read_segment(lazy=lazy))
  60. block.segments[-1].block = block
  61. return block
  62. def read_segment(self, lazy=False):
  63. assert not lazy, 'Do not support lazy'
  64. segment = Segment(file_origin=self.filename)
  65. segment.analogsignals.append(
  66. self.read_analogsignal(lazy=lazy))
  67. segment.analogsignals[-1].segment = segment
  68. return segment
  69. def read_analogsignal(self, path=None, lazy=False):
  70. assert not lazy, 'Do not support lazy'
  71. if not HAVE_IGOR:
  72. raise Exception(("`igor` package not installed. "
  73. "Try `pip install igor`"))
  74. if self.extension == 'ibw':
  75. data = bw.load(self.filename)
  76. version = data['version']
  77. if version > 5:
  78. raise IOError(("Igor binary wave file format version {0} "
  79. "is not supported.".format(version)))
  80. elif self.extension == 'pxp':
  81. assert type(path) is str, \
  82. "A colon-separated Igor-style path must be provided."
  83. _, filesystem = pxp.load(self.filename)
  84. path = path.split(':')
  85. location = filesystem['root']
  86. for element in path:
  87. if element != 'root':
  88. location = location[element.encode('utf8')]
  89. data = location.wave
  90. content = data['wave']
  91. if "padding" in content:
  92. assert content['padding'].size == 0, \
  93. "Cannot handle non-empty padding"
  94. signal = content['wData']
  95. note = content['note']
  96. header = content['wave_header']
  97. name = str(header['bname'].decode('utf-8'))
  98. units = "".join([x.decode() for x in header['dataUnits']])
  99. try:
  100. time_units = "".join([x.decode() for x in header['xUnits']])
  101. assert len(time_units)
  102. except:
  103. time_units = "s"
  104. try:
  105. t_start = pq.Quantity(header['hsB'], time_units)
  106. except KeyError:
  107. t_start = pq.Quantity(header['sfB'][0], time_units)
  108. try:
  109. sampling_period = pq.Quantity(header['hsA'], time_units)
  110. except:
  111. sampling_period = pq.Quantity(header['sfA'][0], time_units)
  112. if self.parse_notes:
  113. try:
  114. annotations = self.parse_notes(note)
  115. except ValueError:
  116. warn("Couldn't parse notes field.")
  117. annotations = {'note': note}
  118. else:
  119. annotations = {'note': note}
  120. signal = AnalogSignal(signal, units=units, copy=False, t_start=t_start,
  121. sampling_period=sampling_period, name=name,
  122. file_origin=self.filename, **annotations)
  123. return signal
  124. # the following function is to handle the annotations in the
  125. # Igor data files from the Blue Brain Project NMC Portal
  126. def key_value_string_parser(itemsep=";", kvsep=":"):
  127. """
  128. Parses a string into a dict.
  129. Arguments:
  130. itemsep - character which separates items
  131. kvsep - character which separates the key and value within an item
  132. Returns:
  133. a function which takes the string to be parsed as the sole argument
  134. and returns a dict.
  135. Example:
  136. >>> parse = key_value_string_parser(itemsep=";", kvsep=":")
  137. >>> parse("a:2;b:3")
  138. {'a': 2, 'b': 3}
  139. """
  140. def parser(s):
  141. items = s.split(itemsep)
  142. return dict(item.split(kvsep, 1) for item in items if item)
  143. return parser