Upgrade to Python 2.7+ string formatting.
[igor.git] / igor / binarywave.py
1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of Hooke.
4 #
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.
9 #
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.
14 #
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/>.
18
19 "Read IGOR Binary Wave files into Numpy arrays."
20
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.
28
29 import array
30 import struct
31 import sys
32 import types
33
34 import numpy
35
36
37 class Field (object):
38     """Represent a Structure field.
39
40     See Also
41     --------
42     Structure
43     """
44     def __init__(self, format, name, default=None, help=None, count=1):
45         self.format = format # See the struct documentation
46         self.name = name
47         self.default = None
48         self.help = help
49         self.count = count
50         self.total_count = numpy.prod(count)
51
52 class Structure (struct.Struct):
53     """Represent a C structure.
54
55     A convenient wrapper around struct.Struct that uses Fields and
56     adds dict-handling methods for transparent name assignment.
57
58     See Also
59     --------
60     Field
61
62     Examples
63     --------
64
65     Represent the C structure::
66
67         struct thing {
68           short version;
69           long size[3];
70         }
71
72     As
73
74     >>> from pprint import pprint
75     >>> thing = Structure(name='thing',
76     ...     fields=[Field('h', 'version'), Field('l', 'size', count=3)])
77     >>> thing.set_byte_order('>')
78     >>> b = array.array('b', range(2+4*3))
79     >>> d = thing.unpack_dict_from(buffer=b)
80     >>> pprint(d)
81     {'size': array([ 33752069, 101124105, 168496141]), 'version': 1}
82     >>> [hex(x) for x in d['size']]
83     ['0x2030405L', '0x6070809L', '0xa0b0c0dL']
84
85     You can even get fancy with multi-dimensional arrays.
86
87     >>> thing = Structure(name='thing',
88     ...     fields=[Field('h', 'version'), Field('l', 'size', count=(3,2))])
89     >>> thing.set_byte_order('>')
90     >>> b = array.array('b', range(2+4*3*2))
91     >>> d = thing.unpack_dict_from(buffer=b)
92     >>> d['size'].shape
93     (3, 2)
94     >>> pprint(d)
95     {'size': array([[ 33752069, 101124105],
96            [168496141, 235868177],
97            [303240213, 370612249]]),
98      'version': 1}
99     """
100     def __init__(self, name, fields, byte_order='='):
101         # '=' for native byte order, standard size and alignment
102         # See http://docs.python.org/library/struct for details
103         self.name = name
104         self.fields = fields
105         self.set_byte_order(byte_order)
106
107     def __str__(self):
108         return self.name
109
110     def set_byte_order(self, byte_order):
111         """Allow changing the format byte_order on the fly.
112         """
113         if (hasattr(self, 'format') and self.format != None
114             and self.format.startswith(byte_order)):
115             return  # no need to change anything
116         format = []
117         for field in self.fields:
118             format.extend([field.format]*field.total_count)
119         struct.Struct.__init__(self, format=byte_order+''.join(format).replace('P', 'L'))
120
121     def _flatten_args(self, args):
122         # handle Field.count > 0
123         flat_args = []
124         for a,f in zip(args, self.fields):
125             if f.total_count > 1:
126                 flat_args.extend(a)
127             else:
128                 flat_args.append(a)
129         return flat_args
130
131     def _unflatten_args(self, args):
132         # handle Field.count > 0
133         unflat_args = []
134         i = 0
135         for f in self.fields:
136             if f.total_count > 1:
137                 data = numpy.array(args[i:i+f.total_count])
138                 data = data.reshape(f.count)
139                 unflat_args.append(data)
140             else:
141                 unflat_args.append(args[i])
142             i += f.total_count
143         return unflat_args
144         
145     def pack(self, *args):
146         return struct.Struct.pack(self, *self._flatten_args(args))
147
148     def pack_into(self, buffer, offset, *args):
149         return struct.Struct.pack_into(self, buffer, offset,
150                                        *self._flatten_args(args))
151
152     def _clean_dict(self, dict):
153         for f in self.fields:
154             if f.name not in dict:
155                 if f.default != None:
156                     dict[f.name] = f.default
157                 else:
158                     raise ValueError('{} field not set for {}'.format(
159                             f.name, self.__class__.__name__))
160         return dict
161
162     def pack_dict(self, dict):
163         dict = self._clean_dict(dict)
164         return self.pack(*[dict[f.name] for f in self.fields])
165
166     def pack_dict_into(self, buffer, offset, dict={}):
167         dict = self._clean_dict(dict)
168         return self.pack_into(buffer, offset,
169                               *[dict[f.name] for f in self.fields])
170
171     def unpack(self, string):
172         return self._unflatten_args(struct.Struct.unpack(self, string))
173
174     def unpack_from(self, buffer, offset=0):
175         return self._unflatten_args(
176             struct.Struct.unpack_from(self, buffer, offset))
177
178     def unpack_dict(self, string):
179         return dict(zip([f.name for f in self.fields],
180                         self.unpack(string)))
181
182     def unpack_dict_from(self, buffer, offset=0):
183         return dict(zip([f.name for f in self.fields],
184                         self.unpack_from(buffer, offset)))
185
186
187 # Numpy doesn't support complex integers by default, see
188 #   http://mail.python.org/pipermail/python-dev/2002-April/022408.html
189 #   http://mail.scipy.org/pipermail/numpy-discussion/2007-October/029447.html
190 # So we roll our own types.  See
191 #   http://docs.scipy.org/doc/numpy/user/basics.rec.html
192 #   http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html
193 complexInt8 = numpy.dtype([('real', numpy.int8), ('imag', numpy.int8)])
194 complexInt16 = numpy.dtype([('real', numpy.int16), ('imag', numpy.int16)])
195 complexInt32 = numpy.dtype([('real', numpy.int32), ('imag', numpy.int32)])
196 complexUInt8 = numpy.dtype([('real', numpy.uint8), ('imag', numpy.uint8)])
197 complexUInt16 = numpy.dtype([('real', numpy.uint16), ('imag', numpy.uint16)])
198 complexUInt32 = numpy.dtype([('real', numpy.uint32), ('imag', numpy.uint32)])
199
200
201 # Begin IGOR constants and typedefs from IgorBin.h
202
203 # From IgorMath.h
204 TYPE_TABLE = {       # (key: integer flag, value: numpy dtype)
205     0:None,          # Text wave, not handled in ReadWave.c
206     1:numpy.complex, # NT_CMPLX, makes number complex.
207     2:numpy.float32, # NT_FP32, 32 bit fp numbers.
208     3:numpy.complex64,
209     4:numpy.float64, # NT_FP64, 64 bit fp numbers.
210     5:numpy.complex128,
211     8:numpy.int8,    # NT_I8, 8 bit signed integer. Requires Igor Pro
212                      # 2.0 or later.
213     9:complexInt8,
214     0x10:numpy.int16,# NT_I16, 16 bit integer numbers. Requires Igor
215                      # Pro 2.0 or later.
216     0x11:complexInt16,
217     0x20:numpy.int32,# NT_I32, 32 bit integer numbers. Requires Igor
218                      # Pro 2.0 or later.
219     0x21:complexInt32,
220 #   0x40:None,       # NT_UNSIGNED, Makes above signed integers
221 #                    # unsigned. Requires Igor Pro 3.0 or later.
222     0x48:numpy.uint8,
223     0x49:complexUInt8,
224     0x50:numpy.uint16,
225     0x51:complexUInt16,
226     0x60:numpy.uint32,
227     0x61:complexUInt32,
228 }
229
230 # From wave.h
231 MAXDIMS = 4
232
233 # From binary.h
234 BinHeaderCommon = Structure(  # WTK: this one is mine.
235     name='BinHeaderCommon',
236     fields=[
237         Field('h', 'version', help='Version number for backwards compatibility.'),
238         ])
239
240 BinHeader1 = Structure(
241     name='BinHeader1',
242     fields=[
243         Field('h', 'version', help='Version number for backwards compatibility.'),
244         Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
245         Field('h', 'checksum', help='Checksum over this header and the wave header.'),
246         ])
247
248 BinHeader2 = Structure(
249     name='BinHeader2',
250     fields=[
251         Field('h', 'version', help='Version number for backwards compatibility.'),
252         Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
253         Field('l', 'noteSize', help='The size of the note text.'),
254         Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'),
255         Field('h', 'checksum', help='Checksum over this header and the wave header.'),
256         ])
257
258 BinHeader3 = Structure(
259     name='BinHeader3',
260     fields=[
261         Field('h', 'version', help='Version number for backwards compatibility.'),
262         Field('h', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
263         Field('l', 'noteSize', help='The size of the note text.'),
264         Field('l', 'formulaSize', help='The size of the dependency formula, if any.'),
265         Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'),
266         Field('h', 'checksum', help='Checksum over this header and the wave header.'),
267         ])
268
269 BinHeader5 = Structure(
270     name='BinHeader5',
271     fields=[
272         Field('h', 'version', help='Version number for backwards compatibility.'),
273         Field('h', 'checksum', help='Checksum over this header and the wave header.'),
274         Field('l', 'wfmSize', help='The size of the WaveHeader5 data structure plus the wave data.'),
275         Field('l', 'formulaSize', help='The size of the dependency formula, if any.'),
276         Field('l', 'noteSize', help='The size of the note text.'),
277         Field('l', 'dataEUnitsSize', help='The size of optional extended data units.'),
278         Field('l', 'dimEUnitsSize', help='The size of optional extended dimension units.', count=MAXDIMS),
279         Field('l', 'dimLabelsSize', help='The size of optional dimension labels.', count=MAXDIMS),
280         Field('l', 'sIndicesSize', help='The size of string indicies if this is a text wave.'),
281         Field('l', 'optionsSize1', default=0, help='Reserved. Write zero. Ignore on read.'),
282         Field('l', 'optionsSize2', default=0, help='Reserved. Write zero. Ignore on read.'),
283         ])
284
285
286 # From wave.h
287 MAX_WAVE_NAME2 = 18 # Maximum length of wave name in version 1 and 2
288                     # files. Does not include the trailing null.
289 MAX_WAVE_NAME5 = 31 # Maximum length of wave name in version 5
290                     # files. Does not include the trailing null.
291 MAX_UNIT_CHARS = 3
292
293 # Header to an array of waveform data.
294
295 WaveHeader2 = Structure(
296     name='WaveHeader2',
297     fields=[
298         Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'),
299         Field('P', 'next', default=0, help='Used in memory only. Write zero. Ignore on read.'),
300         Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME2+2),
301         Field('h', 'whVersion', default=0, help='Write 0. Ignore on read.'),
302         Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'),
303         Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'),
304         Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1),
305         Field('c', 'xUnits', default=0, help='Natural x-axis units go here - null if none.', count=MAX_UNIT_CHARS+1),
306         Field('l', 'npnts', help='Number of data points in wave.'),
307         Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
308         Field('d', 'hsA', help='X value for point p = hsA*p + hsB'),
309         Field('d', 'hsB', help='X value for point p = hsA*p + hsB'),
310         Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
311         Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
312         Field('h', 'fsValid', help='True if full scale values have meaning.'),
313         Field('d', 'topFullScale', help='The min full scale value for wave.'), # sic, 'min' should probably be 'max'
314         Field('d', 'botFullScale', help='The min full scale value for wave.'),
315         Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
316         Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'),
317         Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'),
318         Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'),
319         Field('L', 'creationDate', help='DateTime of creation.  Not used in version 1 files.'),
320         Field('c', 'wUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=2),
321         Field('L', 'modDate', help='DateTime of last modification.'),
322         Field('P', 'waveNoteH', help='Used in memory only. Write zero. Ignore on read.'),
323         Field('f', 'wData', help='The start of the array of waveform data.', count=4),
324         ])
325
326 WaveHeader5 = Structure(
327     name='WaveHeader5',
328     fields=[
329         Field('P', 'next', help='link to next wave in linked list.'),
330         Field('L', 'creationDate', help='DateTime of creation.'),
331         Field('L', 'modDate', help='DateTime of last modification.'),
332         Field('l', 'npnts', help='Total number of points (multiply dimensions up to first zero).'),
333         Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'),
334         Field('h', 'dLock', default=0, help='Reserved. Write zero. Ignore on read.'),
335         Field('c', 'whpad1', default=0, help='Reserved. Write zero. Ignore on read.', count=6),
336         Field('h', 'whVersion', default=1, help='Write 1. Ignore on read.'),
337         Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME5+1),
338         Field('l', 'whpad2', default=0, help='Reserved. Write zero. Ignore on read.'),
339         Field('P', 'dFolder', default=0, help='Used in memory only. Write zero. Ignore on read.'),
340         # Dimensioning info. [0] == rows, [1] == cols etc
341         Field('l', 'nDim', help='Number of of items in a dimension -- 0 means no data.', count=MAXDIMS),
342         Field('d', 'sfA', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS),
343         Field('d', 'sfB', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS),
344         # SI units
345         Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1),
346         Field('c', 'dimUnits', default=0, help='Natural dimension units go here - null if none.', count=(MAXDIMS, MAX_UNIT_CHARS+1)),
347         Field('h', 'fsValid', help='TRUE if full scale values have meaning.'),
348         Field('h', 'whpad3', default=0, help='Reserved. Write zero. Ignore on read.'),
349         Field('d', 'topFullScale', help='The max and max full scale value for wave'), # sic, probably "max and min"
350         Field('d', 'botFullScale', help='The max and max full scale value for wave.'), # sic, probably "max and min"
351         Field('P', 'dataEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
352         Field('P', 'dimEUnits', default=0, help='Used in memory only. Write zero.  Ignore on read.', count=MAXDIMS),
353         Field('P', 'dimLabels', default=0, help='Used in memory only. Write zero.  Ignore on read.', count=MAXDIMS),
354         Field('P', 'waveNoteH', default=0, help='Used in memory only. Write zero. Ignore on read.'),
355         Field('l', 'whUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=16),
356         # The following stuff is considered private to Igor.
357         Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
358         Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
359         Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
360         Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
361         Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'),
362         Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'),
363         Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'),
364         Field('h', 'whpad4', default=0, help='Reserved. Write zero. Ignore on read.'),
365         Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'),
366         Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'),
367         Field('P', 'sIndices', default=0, help='Used in memory only. Write zero. Ignore on read.'),
368         Field('f', 'wData', help='The start of the array of data.  Must be 64 bit aligned.', count=1),
369         ])
370
371 # End IGOR constants and typedefs from IgorBin.h
372
373 # Begin functions from ReadWave.c
374
375 def need_to_reorder_bytes(version):
376     # If the low order byte of the version field of the BinHeader
377     # structure is zero then the file is from a platform that uses
378     # different byte-ordering and therefore all data will need to be
379     # reordered.
380     return version & 0xFF == 0
381
382 def byte_order(needToReorderBytes):
383     little_endian = sys.byteorder == 'little'
384     if needToReorderBytes:
385         little_endian = not little_endian
386     if little_endian:
387         return '<'  # little-endian
388     return '>'  # big-endian    
389
390 def version_structs(version, byte_order):
391     if version == 1:
392         bin = BinHeader1
393         wave = WaveHeader2
394     elif version == 2:
395         bin = BinHeader2
396         wave = WaveHeader2
397     elif version == 3:
398         bin = BinHeader3
399         wave = WaveHeader2
400     elif version == 5:
401         bin = BinHeader5
402         wave = WaveHeader5
403     else:
404         raise ValueError(
405             ('This does not appear to be a valid Igor binary wave file. '
406              'The version field = {}.\n').format(version))
407     checkSumSize = bin.size + wave.size
408     if version == 5:
409         checkSumSize -= 4  # Version 5 checksum does not include the wData field.
410     bin.set_byte_order(byte_order)
411     wave.set_byte_order(byte_order)
412     return (bin, wave, checkSumSize)
413
414 def checksum(buffer, byte_order, oldcksum, numbytes):
415     x = numpy.ndarray(
416         (numbytes/2,), # 2 bytes to a short -- ignore trailing odd byte
417         dtype=numpy.dtype(byte_order+'h'),
418         buffer=buffer)
419     oldcksum += x.sum()
420     if oldcksum > 2**31:  # fake the C implementation's int rollover
421         oldcksum %= 2**32
422         if oldcksum > 2**31:
423             oldcksum -= 2**31
424     return oldcksum & 0xffff
425
426 # Translated from ReadWave()
427 def loadibw(filename, strict=True):
428     if hasattr(filename, 'read'):
429         f = filename  # filename is actually a stream object
430     else:
431         f = open(filename, 'rb')
432     try:
433         b = buffer(f.read(BinHeaderCommon.size))
434         version = BinHeaderCommon.unpack_dict_from(b)['version']
435         needToReorderBytes = need_to_reorder_bytes(version)
436         byteOrder = byte_order(needToReorderBytes)
437         
438         if needToReorderBytes:
439             BinHeaderCommon.set_byte_order(byteOrder)
440             version = BinHeaderCommon.unpack_dict_from(b)['version']
441         bin_struct,wave_struct,checkSumSize = version_structs(version, byteOrder)
442
443         b = buffer(b + f.read(bin_struct.size + wave_struct.size - BinHeaderCommon.size))
444         c = checksum(b, byteOrder, 0, checkSumSize)
445         if c != 0:
446             raise ValueError(
447                 ('This does not appear to be a valid Igor binary wave file.  '
448                  'Error in checksum: should be 0, is {}.').format(c))
449         bin_info = bin_struct.unpack_dict_from(b)
450         wave_info = wave_struct.unpack_dict_from(b, offset=bin_struct.size)
451         if wave_info['type'] == 0:
452             raise NotImplementedError('Text wave')
453         if version in [1,2,3]:
454             tail = 16  # 16 = size of wData field in WaveHeader2 structure
455             waveDataSize = bin_info['wfmSize'] - wave_struct.size
456             # =  bin_info['wfmSize']-16 - (wave_struct.size - tail)
457         else:
458             assert version == 5, version
459             tail = 4  # 4 = size of wData field in WaveHeader5 structure
460             waveDataSize = bin_info['wfmSize'] - (wave_struct.size - tail)
461         # dtype() wrapping to avoid numpy.generic and
462         # getset_descriptor issues with the builtin Numpy types
463         # (e.g. int32).  It has no effect on our local complex
464         # integers.
465         t = numpy.dtype(TYPE_TABLE[wave_info['type']])
466         assert waveDataSize == wave_info['npnts'] * t.itemsize, (
467             '{}, {}, {}, {}'.format(
468                 waveDataSize, wave_info['npnts'], t.itemsize, t))
469         tail_data = array.array('f', b[-tail:])
470         data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail))
471         if version == 5:
472             shape = [n for n in wave_info['nDim'] if n > 0]
473         else:
474             shape = (wave_info['npnts'],)
475         data = numpy.ndarray(
476             shape=shape,
477             dtype=t.newbyteorder(byteOrder),
478             buffer=data_b,
479             order='F',
480             )
481
482         if version == 1:
483             pass  # No post-data information
484         elif version == 2:
485             # Post-data info:
486             #   * 16 bytes of padding
487             #   * Optional wave note data
488             pad_b = buffer(f.read(16))  # skip the padding
489             if max(pad_b) != 0:
490                 if strict:
491                     assert max(pad_b) == 0, pad_b
492                 else:
493                     sys.stderr.write(
494                         'warning: post-data padding not zero: {}\n'.format(
495                             pad_b))
496             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
497         elif version == 3:
498             # Post-data info:
499             #   * 16 bytes of padding
500             #   * Optional wave note data
501             #   * Optional wave dependency formula
502             """Excerpted from TN003:
503
504             A wave has a dependency formula if it has been bound by a
505             statement such as "wave0 := sin(x)". In this example, the
506             dependency formula is "sin(x)". The formula is stored with
507             no trailing null byte.
508             """
509             pad_b = buffer(f.read(16))  # skip the padding
510             if max(pad_b) != 0:
511                 if strict:
512                     assert max(pad_b) == 0, pad_b
513                 else:
514                     sys.stderr.write(
515                         'warning: post-data padding not zero: {}\n'.format(
516                             pad_b))
517             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
518             bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
519         elif version == 5:
520             # Post-data info:
521             #   * Optional wave dependency formula
522             #   * Optional wave note data
523             #   * Optional extended data units data
524             #   * Optional extended dimension units data
525             #   * Optional dimension label data
526             #   * String indices used for text waves only
527             """Excerpted from TN003:
528
529             dataUnits - Present in versions 1, 2, 3, 5. The dataUnits
530               field stores the units for the data represented by the
531               wave. It is a C string terminated with a null
532               character. This field supports units of 0 to 3 bytes. In
533               version 1, 2 and 3 files, longer units can not be
534               represented. In version 5 files, longer units can be
535               stored using the optional extended data units section of
536               the file.
537
538             xUnits - Present in versions 1, 2, 3. The xUnits field
539               stores the X units for a wave. It is a C string
540               terminated with a null character.  This field supports
541               units of 0 to 3 bytes. In version 1, 2 and 3 files,
542               longer units can not be represented.
543
544             dimUnits - Present in version 5 only. This field is an
545               array of 4 strings, one for each possible wave
546               dimension. Each string supports units of 0 to 3
547               bytes. Longer units can be stored using the optional
548               extended dimension units section of the file.
549             """
550             bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
551             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
552             bin_info['dataEUnits'] = str(f.read(bin_info['dataEUnitsSize'])).strip()
553             bin_info['dimEUnits'] = [
554                 str(f.read(size)).strip() for size in bin_info['dimEUnitsSize']]
555             bin_info['dimLabels'] = []
556             for size in bin_info['dimLabelsSize']:
557                 labels = str(f.read(size)).split(chr(0)) # split null-delimited strings
558                 bin_info['dimLabels'].append([L for L in labels if len(L) > 0])
559             if wave_info['type'] == 0:  # text wave
560                 bin_info['sIndices'] = f.read(bin_info['sIndicesSize'])
561
562     finally:
563         if not hasattr(filename, 'read'):
564             f.close()
565
566     return data, bin_info, wave_info
567
568
569 def saveibw(filename):
570     raise NotImplementedError