Restore native byte order before running need_to_reorder_bytes.
[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 def hex_bytes(buffer, spaces=None):
427     r"""Pretty-printing for binary buffers.
428
429     >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'))
430     '0001020304'
431     >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=1)
432     '00 01 02 03 04'
433     >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=2)
434     '0001 0203 04'
435     >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=2)
436     '0001 0203 0405 06'
437     >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=3)
438     '000102 030405 06'
439     """
440     hex_bytes = ['{:02x}'.format(ord(x)) for x in buffer]
441     if spaces is None:
442         return ''.join(hex_bytes)
443     elif spaces is 1:
444         return ' '.join(hex_bytes)
445     for i in range(len(hex_bytes)//spaces):
446         hex_bytes.insert((spaces+1)*(i+1)-1, ' ')
447     return ''.join(hex_bytes)
448
449 def assert_null(buffer, strict=True):
450     r"""Ensure an input buffer is entirely zero.
451
452     >>> assert_null(buffer(''))
453     >>> assert_null(buffer('\x00\x00'))
454     >>> assert_null(buffer('\x00\x01\x02\x03'))
455     Traceback (most recent call last):
456       ...
457     ValueError: 00 01 02 03
458     >>> stderr = sys.stderr
459     >>> sys.stderr = sys.stdout
460     >>> assert_null(buffer('\x00\x01\x02\x03'), strict=False)
461     warning: post-data padding not zero: 00 01 02 03
462     >>> sys.stderr = stderr
463     """
464     if buffer and ord(max(buffer)) != 0:
465         hex_string = hex_bytes(buffer, spaces=1)
466         if strict:
467             raise ValueError(hex_string)
468         else:
469             sys.stderr.write(
470                 'warning: post-data padding not zero: {}\n'.format(hex_string))
471
472 # Translated from ReadWave()
473 def loadibw(filename, strict=True):
474     if hasattr(filename, 'read'):
475         f = filename  # filename is actually a stream object
476     else:
477         f = open(filename, 'rb')
478     try:
479         BinHeaderCommon.set_byte_order('=')
480         b = buffer(f.read(BinHeaderCommon.size))
481         version = BinHeaderCommon.unpack_dict_from(b)['version']
482         needToReorderBytes = need_to_reorder_bytes(version)
483         byteOrder = byte_order(needToReorderBytes)
484         
485         if needToReorderBytes:
486             BinHeaderCommon.set_byte_order(byteOrder)
487             version = BinHeaderCommon.unpack_dict_from(b)['version']
488         bin_struct,wave_struct,checkSumSize = version_structs(version, byteOrder)
489
490         b = buffer(b + f.read(bin_struct.size + wave_struct.size - BinHeaderCommon.size))
491         c = checksum(b, byteOrder, 0, checkSumSize)
492         if c != 0:
493             raise ValueError(
494                 ('This does not appear to be a valid Igor binary wave file.  '
495                  'Error in checksum: should be 0, is {}.').format(c))
496         bin_info = bin_struct.unpack_dict_from(b)
497         wave_info = wave_struct.unpack_dict_from(b, offset=bin_struct.size)
498         if wave_info['type'] == 0:
499             raise NotImplementedError('Text wave')
500         if version in [1,2,3]:
501             tail = 16  # 16 = size of wData field in WaveHeader2 structure
502             waveDataSize = bin_info['wfmSize'] - wave_struct.size
503             # =  bin_info['wfmSize']-16 - (wave_struct.size - tail)
504         else:
505             assert version == 5, version
506             tail = 4  # 4 = size of wData field in WaveHeader5 structure
507             waveDataSize = bin_info['wfmSize'] - (wave_struct.size - tail)
508         # dtype() wrapping to avoid numpy.generic and
509         # getset_descriptor issues with the builtin Numpy types
510         # (e.g. int32).  It has no effect on our local complex
511         # integers.
512         t = numpy.dtype(TYPE_TABLE[wave_info['type']])
513         assert waveDataSize == wave_info['npnts'] * t.itemsize, (
514             '{}, {}, {}, {}'.format(
515                 waveDataSize, wave_info['npnts'], t.itemsize, t))
516         tail_data = array.array('f', b[-tail:])
517         data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail))
518         if version == 5:
519             shape = [n for n in wave_info['nDim'] if n > 0]
520         else:
521             shape = (wave_info['npnts'],)
522         data = numpy.ndarray(
523             shape=shape,
524             dtype=t.newbyteorder(byteOrder),
525             buffer=data_b,
526             order='F',
527             )
528
529         if version == 1:
530             pass  # No post-data information
531         elif version == 2:
532             # Post-data info:
533             #   * 16 bytes of padding
534             #   * Optional wave note data
535             pad_b = buffer(f.read(16))  # skip the padding
536             assert_null(pad_b, strict=strict)
537             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
538         elif version == 3:
539             # Post-data info:
540             #   * 16 bytes of padding
541             #   * Optional wave note data
542             #   * Optional wave dependency formula
543             """Excerpted from TN003:
544
545             A wave has a dependency formula if it has been bound by a
546             statement such as "wave0 := sin(x)". In this example, the
547             dependency formula is "sin(x)". The formula is stored with
548             no trailing null byte.
549             """
550             pad_b = buffer(f.read(16))  # skip the padding
551             assert_null(pad_b, strict=strict)
552             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
553             bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
554         elif version == 5:
555             # Post-data info:
556             #   * Optional wave dependency formula
557             #   * Optional wave note data
558             #   * Optional extended data units data
559             #   * Optional extended dimension units data
560             #   * Optional dimension label data
561             #   * String indices used for text waves only
562             """Excerpted from TN003:
563
564             dataUnits - Present in versions 1, 2, 3, 5. The dataUnits
565               field stores the units for the data represented by the
566               wave. It is a C string terminated with a null
567               character. This field supports units of 0 to 3 bytes. In
568               version 1, 2 and 3 files, longer units can not be
569               represented. In version 5 files, longer units can be
570               stored using the optional extended data units section of
571               the file.
572
573             xUnits - Present in versions 1, 2, 3. The xUnits field
574               stores the X units for a wave. It is a C string
575               terminated with a null character.  This field supports
576               units of 0 to 3 bytes. In version 1, 2 and 3 files,
577               longer units can not be represented.
578
579             dimUnits - Present in version 5 only. This field is an
580               array of 4 strings, one for each possible wave
581               dimension. Each string supports units of 0 to 3
582               bytes. Longer units can be stored using the optional
583               extended dimension units section of the file.
584             """
585             bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
586             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
587             bin_info['dataEUnits'] = str(f.read(bin_info['dataEUnitsSize'])).strip()
588             bin_info['dimEUnits'] = [
589                 str(f.read(size)).strip() for size in bin_info['dimEUnitsSize']]
590             bin_info['dimLabels'] = []
591             for size in bin_info['dimLabelsSize']:
592                 labels = str(f.read(size)).split(chr(0)) # split null-delimited strings
593                 bin_info['dimLabels'].append([L for L in labels if len(L) > 0])
594             if wave_info['type'] == 0:  # text wave
595                 bin_info['sIndices'] = f.read(bin_info['sIndicesSize'])
596
597     finally:
598         if not hasattr(filename, 'read'):
599             f.close()
600
601     return data, bin_info, wave_info
602
603
604 def saveibw(filename):
605     raise NotImplementedError