1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
13 # Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with Hooke. If not, see
17 # <http://www.gnu.org/licenses/>.
19 "Read IGOR Binary Wave files into Numpy arrays."
21 # Based on WaveMetric's Technical Note 003, "Igor Binary Format"
22 # ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN003.zip
23 # From ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN000.txt
24 # We place no restrictions on copying Technical Notes, with the
25 # exception that you cannot resell them. So read, enjoy, and
26 # share. We hope IGOR Technical Notes will provide you with lots of
27 # valuable information while you are developing IGOR applications.
37 _buffer = buffer # save builtin buffer for clobbered situations
41 """Represent a Structure field.
47 def __init__(self, format, name, default=None, help=None, count=1):
48 self.format = format # See the struct documentation
53 self.total_count = numpy.prod(count)
55 class Structure (struct.Struct):
56 """Represent a C structure.
58 A convenient wrapper around struct.Struct that uses Fields and
59 adds dict-handling methods for transparent name assignment.
68 Represent the C structure::
77 >>> from pprint import pprint
78 >>> thing = Structure(name='thing',
79 ... fields=[Field('h', 'version'), Field('l', 'size', count=3)])
80 >>> thing.set_byte_order('>')
81 >>> b = array.array('b', range(2+4*3))
82 >>> d = thing.unpack_dict_from(buffer=b)
84 {'size': array([ 33752069, 101124105, 168496141]), 'version': 1}
85 >>> [hex(x) for x in d['size']]
86 ['0x2030405L', '0x6070809L', '0xa0b0c0dL']
88 You can even get fancy with multi-dimensional arrays.
90 >>> thing = Structure(name='thing',
91 ... fields=[Field('h', 'version'), Field('l', 'size', count=(3,2))])
92 >>> thing.set_byte_order('>')
93 >>> b = array.array('b', range(2+4*3*2))
94 >>> d = thing.unpack_dict_from(buffer=b)
98 {'size': array([[ 33752069, 101124105],
99 [168496141, 235868177],
100 [303240213, 370612249]]),
103 def __init__(self, name, fields, byte_order='='):
104 # '=' for native byte order, standard size and alignment
105 # See http://docs.python.org/library/struct for details
108 self.set_byte_order(byte_order)
113 def set_byte_order(self, byte_order):
114 """Allow changing the format byte_order on the fly.
116 if (hasattr(self, 'format') and self.format != None
117 and self.format.startswith(byte_order)):
118 return # no need to change anything
120 for field in self.fields:
121 format.extend([field.format]*field.total_count)
122 struct.Struct.__init__(self, format=byte_order+''.join(format).replace('P', 'L'))
124 def _flatten_args(self, args):
125 # handle Field.count > 0
127 for a,f in zip(args, self.fields):
128 if f.total_count > 1:
134 def _unflatten_args(self, args):
135 # handle Field.count > 0
138 for f in self.fields:
139 if f.total_count > 1:
140 data = numpy.array(args[i:i+f.total_count])
141 data = data.reshape(f.count)
142 unflat_args.append(data)
144 unflat_args.append(args[i])
148 def pack(self, *args):
149 return struct.Struct.pack(self, *self._flatten_args(args))
151 def pack_into(self, buffer, offset, *args):
152 return struct.Struct.pack_into(self, buffer, offset,
153 *self._flatten_args(args))
155 def _clean_dict(self, dict):
156 for f in self.fields:
157 if f.name not in dict:
158 if f.default != None:
159 dict[f.name] = f.default
161 raise ValueError('{} field not set for {}'.format(
162 f.name, self.__class__.__name__))
165 def pack_dict(self, dict):
166 dict = self._clean_dict(dict)
167 return self.pack(*[dict[f.name] for f in self.fields])
169 def pack_dict_into(self, buffer, offset, dict={}):
170 dict = self._clean_dict(dict)
171 return self.pack_into(buffer, offset,
172 *[dict[f.name] for f in self.fields])
174 def unpack(self, string):
175 return self._unflatten_args(struct.Struct.unpack(self, string))
177 def unpack_from(self, buffer, offset=0):
179 args = struct.Struct.unpack_from(self, buffer, offset)
180 except struct.error as e:
181 if not self.name in ('WaveHeader2', 'WaveHeader5'):
183 # HACK! For WaveHeader5, when npnts is 0, wData is
184 # optional. If we couldn't unpack the structure, fill in
185 # wData with zeros and try again, asserting that npnts is
187 if len(buffer) - offset < self.size:
188 # missing wData? Pad with zeros
189 buffer += _buffer('\x00'*(self.size + offset - len(buffer)))
190 args = struct.Struct.unpack_from(self, buffer, offset)
191 unpacked = self._unflatten_args(args)
192 data = dict(zip([f.name for f in self.fields],
194 assert data['npnts'] == 0, data['npnts']
195 return self._unflatten_args(args)
197 def unpack_dict(self, string):
198 return dict(zip([f.name for f in self.fields],
199 self.unpack(string)))
201 def unpack_dict_from(self, buffer, offset=0):
202 return dict(zip([f.name for f in self.fields],
203 self.unpack_from(buffer, offset)))
206 # Numpy doesn't support complex integers by default, see
207 # http://mail.python.org/pipermail/python-dev/2002-April/022408.html
208 # http://mail.scipy.org/pipermail/numpy-discussion/2007-October/029447.html
209 # So we roll our own types. See
210 # http://docs.scipy.org/doc/numpy/user/basics.rec.html
211 # http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html
212 complexInt8 = numpy.dtype([('real', numpy.int8), ('imag', numpy.int8)])
213 complexInt16 = numpy.dtype([('real', numpy.int16), ('imag', numpy.int16)])
214 complexInt32 = numpy.dtype([('real', numpy.int32), ('imag', numpy.int32)])
215 complexUInt8 = numpy.dtype([('real', numpy.uint8), ('imag', numpy.uint8)])
216 complexUInt16 = numpy.dtype([('real', numpy.uint16), ('imag', numpy.uint16)])
217 complexUInt32 = numpy.dtype([('real', numpy.uint32), ('imag', numpy.uint32)])
220 # Begin IGOR constants and typedefs from IgorBin.h
223 TYPE_TABLE = { # (key: integer flag, value: numpy dtype)
224 0:None, # Text wave, not handled in ReadWave.c
225 1:numpy.complex, # NT_CMPLX, makes number complex.
226 2:numpy.float32, # NT_FP32, 32 bit fp numbers.
228 4:numpy.float64, # NT_FP64, 64 bit fp numbers.
230 8:numpy.int8, # NT_I8, 8 bit signed integer. Requires Igor Pro
233 0x10:numpy.int16,# NT_I16, 16 bit integer numbers. Requires Igor
236 0x20:numpy.int32,# NT_I32, 32 bit integer numbers. Requires Igor
239 # 0x40:None, # NT_UNSIGNED, Makes above signed integers
240 # # unsigned. Requires Igor Pro 3.0 or later.
253 BinHeaderCommon = Structure( # WTK: this one is mine.
254 name='BinHeaderCommon',
256 Field('h', 'version', help='Version number for backwards compatibility.'),
259 BinHeader1 = Structure(
262 Field('h', 'version', help='Version number for backwards compatibility.'),
263 Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
264 Field('h', 'checksum', help='Checksum over this header and the wave header.'),
267 BinHeader2 = Structure(
270 Field('h', 'version', help='Version number for backwards compatibility.'),
271 Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
272 Field('l', 'noteSize', help='The size of the note text.'),
273 Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'),
274 Field('h', 'checksum', help='Checksum over this header and the wave header.'),
277 BinHeader3 = Structure(
280 Field('h', 'version', help='Version number for backwards compatibility.'),
281 Field('h', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
282 Field('l', 'noteSize', help='The size of the note text.'),
283 Field('l', 'formulaSize', help='The size of the dependency formula, if any.'),
284 Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'),
285 Field('h', 'checksum', help='Checksum over this header and the wave header.'),
288 BinHeader5 = Structure(
291 Field('h', 'version', help='Version number for backwards compatibility.'),
292 Field('h', 'checksum', help='Checksum over this header and the wave header.'),
293 Field('l', 'wfmSize', help='The size of the WaveHeader5 data structure plus the wave data.'),
294 Field('l', 'formulaSize', help='The size of the dependency formula, if any.'),
295 Field('l', 'noteSize', help='The size of the note text.'),
296 Field('l', 'dataEUnitsSize', help='The size of optional extended data units.'),
297 Field('l', 'dimEUnitsSize', help='The size of optional extended dimension units.', count=MAXDIMS),
298 Field('l', 'dimLabelsSize', help='The size of optional dimension labels.', count=MAXDIMS),
299 Field('l', 'sIndicesSize', help='The size of string indicies if this is a text wave.'),
300 Field('l', 'optionsSize1', default=0, help='Reserved. Write zero. Ignore on read.'),
301 Field('l', 'optionsSize2', default=0, help='Reserved. Write zero. Ignore on read.'),
306 MAX_WAVE_NAME2 = 18 # Maximum length of wave name in version 1 and 2
307 # files. Does not include the trailing null.
308 MAX_WAVE_NAME5 = 31 # Maximum length of wave name in version 5
309 # files. Does not include the trailing null.
312 # Header to an array of waveform data.
314 WaveHeader2 = Structure(
317 Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'),
318 Field('P', 'next', default=0, help='Used in memory only. Write zero. Ignore on read.'),
319 Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME2+2),
320 Field('h', 'whVersion', default=0, help='Write 0. Ignore on read.'),
321 Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'),
322 Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'),
323 Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1),
324 Field('c', 'xUnits', default=0, help='Natural x-axis units go here - null if none.', count=MAX_UNIT_CHARS+1),
325 Field('l', 'npnts', help='Number of data points in wave.'),
326 Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
327 Field('d', 'hsA', help='X value for point p = hsA*p + hsB'),
328 Field('d', 'hsB', help='X value for point p = hsA*p + hsB'),
329 Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
330 Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
331 Field('h', 'fsValid', help='True if full scale values have meaning.'),
332 Field('d', 'topFullScale', help='The min full scale value for wave.'), # sic, 'min' should probably be 'max'
333 Field('d', 'botFullScale', help='The min full scale value for wave.'),
334 Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
335 Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'),
336 Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'),
337 Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'),
338 Field('L', 'creationDate', help='DateTime of creation. Not used in version 1 files.'),
339 Field('c', 'wUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=2),
340 Field('L', 'modDate', help='DateTime of last modification.'),
341 Field('P', 'waveNoteH', help='Used in memory only. Write zero. Ignore on read.'),
342 Field('f', 'wData', help='The start of the array of waveform data.', count=4),
345 WaveHeader5 = Structure(
348 Field('P', 'next', help='link to next wave in linked list.'),
349 Field('L', 'creationDate', help='DateTime of creation.'),
350 Field('L', 'modDate', help='DateTime of last modification.'),
351 Field('l', 'npnts', help='Total number of points (multiply dimensions up to first zero).'),
352 Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'),
353 Field('h', 'dLock', default=0, help='Reserved. Write zero. Ignore on read.'),
354 Field('c', 'whpad1', default=0, help='Reserved. Write zero. Ignore on read.', count=6),
355 Field('h', 'whVersion', default=1, help='Write 1. Ignore on read.'),
356 Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME5+1),
357 Field('l', 'whpad2', default=0, help='Reserved. Write zero. Ignore on read.'),
358 Field('P', 'dFolder', default=0, help='Used in memory only. Write zero. Ignore on read.'),
359 # Dimensioning info. [0] == rows, [1] == cols etc
360 Field('l', 'nDim', help='Number of of items in a dimension -- 0 means no data.', count=MAXDIMS),
361 Field('d', 'sfA', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS),
362 Field('d', 'sfB', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS),
364 Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1),
365 Field('c', 'dimUnits', default=0, help='Natural dimension units go here - null if none.', count=(MAXDIMS, MAX_UNIT_CHARS+1)),
366 Field('h', 'fsValid', help='TRUE if full scale values have meaning.'),
367 Field('h', 'whpad3', default=0, help='Reserved. Write zero. Ignore on read.'),
368 Field('d', 'topFullScale', help='The max and max full scale value for wave'), # sic, probably "max and min"
369 Field('d', 'botFullScale', help='The max and max full scale value for wave.'), # sic, probably "max and min"
370 Field('P', 'dataEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
371 Field('P', 'dimEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS),
372 Field('P', 'dimLabels', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS),
373 Field('P', 'waveNoteH', default=0, help='Used in memory only. Write zero. Ignore on read.'),
374 Field('l', 'whUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=16),
375 # The following stuff is considered private to Igor.
376 Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
377 Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
378 Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
379 Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
380 Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'),
381 Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'),
382 Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'),
383 Field('h', 'whpad4', default=0, help='Reserved. Write zero. Ignore on read.'),
384 Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'),
385 Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'),
386 Field('P', 'sIndices', default=0, help='Used in memory only. Write zero. Ignore on read.'),
387 Field('f', 'wData', help='The start of the array of data. Must be 64 bit aligned.', count=1),
390 # End IGOR constants and typedefs from IgorBin.h
392 # Begin functions from ReadWave.c
394 def need_to_reorder_bytes(version):
395 # If the low order byte of the version field of the BinHeader
396 # structure is zero then the file is from a platform that uses
397 # different byte-ordering and therefore all data will need to be
399 return version & 0xFF == 0
401 def byte_order(needToReorderBytes):
402 little_endian = sys.byteorder == 'little'
403 if needToReorderBytes:
404 little_endian = not little_endian
406 return '<' # little-endian
407 return '>' # big-endian
409 def version_structs(version, byte_order):
424 ('This does not appear to be a valid Igor binary wave file. '
425 'The version field = {}.\n').format(version))
426 checkSumSize = bin.size + wave.size
428 checkSumSize -= 4 # Version 5 checksum does not include the wData field.
429 bin.set_byte_order(byte_order)
430 wave.set_byte_order(byte_order)
431 return (bin, wave, checkSumSize)
433 def checksum(buffer, byte_order, oldcksum, numbytes):
435 (numbytes/2,), # 2 bytes to a short -- ignore trailing odd byte
436 dtype=numpy.dtype(byte_order+'h'),
439 if oldcksum > 2**31: # fake the C implementation's int rollover
443 return oldcksum & 0xffff
445 def hex_bytes(buffer, spaces=None):
446 r"""Pretty-printing for binary buffers.
448 >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'))
450 >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=1)
452 >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=2)
454 >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=2)
456 >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=3)
459 hex_bytes = ['{:02x}'.format(ord(x)) for x in buffer]
461 return ''.join(hex_bytes)
463 return ' '.join(hex_bytes)
464 for i in range(len(hex_bytes)//spaces):
465 hex_bytes.insert((spaces+1)*(i+1)-1, ' ')
466 return ''.join(hex_bytes)
468 def assert_null(buffer, strict=True):
469 r"""Ensure an input buffer is entirely zero.
471 >>> assert_null(buffer(''))
472 >>> assert_null(buffer('\x00\x00'))
473 >>> assert_null(buffer('\x00\x01\x02\x03'))
474 Traceback (most recent call last):
476 ValueError: 00 01 02 03
477 >>> stderr = sys.stderr
478 >>> sys.stderr = sys.stdout
479 >>> assert_null(buffer('\x00\x01\x02\x03'), strict=False)
480 warning: post-data padding not zero: 00 01 02 03
481 >>> sys.stderr = stderr
483 if buffer and ord(max(buffer)) != 0:
484 hex_string = hex_bytes(buffer, spaces=1)
486 raise ValueError(hex_string)
489 'warning: post-data padding not zero: {}\n'.format(hex_string))
491 # Translated from ReadWave()
492 def loadibw(filename, strict=True):
493 if hasattr(filename, 'read'):
494 f = filename # filename is actually a stream object
496 f = open(filename, 'rb')
498 BinHeaderCommon.set_byte_order('=')
499 b = buffer(f.read(BinHeaderCommon.size))
500 version = BinHeaderCommon.unpack_dict_from(b)['version']
501 needToReorderBytes = need_to_reorder_bytes(version)
502 byteOrder = byte_order(needToReorderBytes)
504 if needToReorderBytes:
505 BinHeaderCommon.set_byte_order(byteOrder)
506 version = BinHeaderCommon.unpack_dict_from(b)['version']
507 bin_struct,wave_struct,checkSumSize = version_structs(version, byteOrder)
509 b = buffer(b + f.read(bin_struct.size + wave_struct.size - BinHeaderCommon.size))
510 c = checksum(b, byteOrder, 0, checkSumSize)
513 ('This does not appear to be a valid Igor binary wave file. '
514 'Error in checksum: should be 0, is {}.').format(c))
515 bin_info = bin_struct.unpack_dict_from(b)
516 wave_info = wave_struct.unpack_dict_from(b, offset=bin_struct.size)
517 if wave_info['type'] == 0:
518 raise NotImplementedError('Text wave')
519 if version in [1,2,3]:
520 tail = 16 # 16 = size of wData field in WaveHeader2 structure
521 waveDataSize = bin_info['wfmSize'] - wave_struct.size
522 # = bin_info['wfmSize']-16 - (wave_struct.size - tail)
524 assert version == 5, version
525 tail = 4 # 4 = size of wData field in WaveHeader5 structure
526 waveDataSize = bin_info['wfmSize'] - (wave_struct.size - tail)
527 # dtype() wrapping to avoid numpy.generic and
528 # getset_descriptor issues with the builtin Numpy types
529 # (e.g. int32). It has no effect on our local complex
531 t = numpy.dtype(TYPE_TABLE[wave_info['type']])
532 assert waveDataSize == wave_info['npnts'] * t.itemsize, (
533 '{}, {}, {}, {}'.format(
534 waveDataSize, wave_info['npnts'], t.itemsize, t))
536 shape = [n for n in wave_info['nDim'] if n > 0] or (0,)
538 shape = (wave_info['npnts'],)
539 if wave_info['npnts'] == 0:
542 tail_data = array.array('f', b[-tail:])
543 data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail))
544 data = numpy.ndarray(
546 dtype=t.newbyteorder(byteOrder),
552 pass # No post-data information
555 # * 16 bytes of padding
556 # * Optional wave note data
557 pad_b = buffer(f.read(16)) # skip the padding
558 assert_null(pad_b, strict=strict)
559 bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
562 # * 16 bytes of padding
563 # * Optional wave note data
564 # * Optional wave dependency formula
565 """Excerpted from TN003:
567 A wave has a dependency formula if it has been bound by a
568 statement such as "wave0 := sin(x)". In this example, the
569 dependency formula is "sin(x)". The formula is stored with
570 no trailing null byte.
572 pad_b = buffer(f.read(16)) # skip the padding
573 assert_null(pad_b, strict=strict)
574 bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
575 bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
578 # * Optional wave dependency formula
579 # * Optional wave note data
580 # * Optional extended data units data
581 # * Optional extended dimension units data
582 # * Optional dimension label data
583 # * String indices used for text waves only
584 """Excerpted from TN003:
586 dataUnits - Present in versions 1, 2, 3, 5. The dataUnits
587 field stores the units for the data represented by the
588 wave. It is a C string terminated with a null
589 character. This field supports units of 0 to 3 bytes. In
590 version 1, 2 and 3 files, longer units can not be
591 represented. In version 5 files, longer units can be
592 stored using the optional extended data units section of
595 xUnits - Present in versions 1, 2, 3. The xUnits field
596 stores the X units for a wave. It is a C string
597 terminated with a null character. This field supports
598 units of 0 to 3 bytes. In version 1, 2 and 3 files,
599 longer units can not be represented.
601 dimUnits - Present in version 5 only. This field is an
602 array of 4 strings, one for each possible wave
603 dimension. Each string supports units of 0 to 3
604 bytes. Longer units can be stored using the optional
605 extended dimension units section of the file.
607 bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
608 bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
609 bin_info['dataEUnits'] = str(f.read(bin_info['dataEUnitsSize'])).strip()
610 bin_info['dimEUnits'] = [
611 str(f.read(size)).strip() for size in bin_info['dimEUnitsSize']]
612 bin_info['dimLabels'] = []
613 for size in bin_info['dimLabelsSize']:
614 labels = str(f.read(size)).split(chr(0)) # split null-delimited strings
615 bin_info['dimLabels'].append([L for L in labels if len(L) > 0])
616 if wave_info['type'] == 0: # text wave
617 bin_info['sIndices'] = f.read(bin_info['sIndicesSize'])
620 if not hasattr(filename, 'read'):
623 return data, bin_info, wave_info
626 def saveibw(filename):
627 raise NotImplementedError