Pull null-buffer check out into its own function: binarywave.assert_null.
[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         b = buffer(f.read(BinHeaderCommon.size))
480         version = BinHeaderCommon.unpack_dict_from(b)['version']
481         needToReorderBytes = need_to_reorder_bytes(version)
482         byteOrder = byte_order(needToReorderBytes)
483         
484         if needToReorderBytes:
485             BinHeaderCommon.set_byte_order(byteOrder)
486             version = BinHeaderCommon.unpack_dict_from(b)['version']
487         bin_struct,wave_struct,checkSumSize = version_structs(version, byteOrder)
488
489         b = buffer(b + f.read(bin_struct.size + wave_struct.size - BinHeaderCommon.size))
490         c = checksum(b, byteOrder, 0, checkSumSize)
491         if c != 0:
492             raise ValueError(
493                 ('This does not appear to be a valid Igor binary wave file.  '
494                  'Error in checksum: should be 0, is {}.').format(c))
495         bin_info = bin_struct.unpack_dict_from(b)
496         wave_info = wave_struct.unpack_dict_from(b, offset=bin_struct.size)
497         if wave_info['type'] == 0:
498             raise NotImplementedError('Text wave')
499         if version in [1,2,3]:
500             tail = 16  # 16 = size of wData field in WaveHeader2 structure
501             waveDataSize = bin_info['wfmSize'] - wave_struct.size
502             # =  bin_info['wfmSize']-16 - (wave_struct.size - tail)
503         else:
504             assert version == 5, version
505             tail = 4  # 4 = size of wData field in WaveHeader5 structure
506             waveDataSize = bin_info['wfmSize'] - (wave_struct.size - tail)
507         # dtype() wrapping to avoid numpy.generic and
508         # getset_descriptor issues with the builtin Numpy types
509         # (e.g. int32).  It has no effect on our local complex
510         # integers.
511         t = numpy.dtype(TYPE_TABLE[wave_info['type']])
512         assert waveDataSize == wave_info['npnts'] * t.itemsize, (
513             '{}, {}, {}, {}'.format(
514                 waveDataSize, wave_info['npnts'], t.itemsize, t))
515         tail_data = array.array('f', b[-tail:])
516         data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail))
517         if version == 5:
518             shape = [n for n in wave_info['nDim'] if n > 0]
519         else:
520             shape = (wave_info['npnts'],)
521         data = numpy.ndarray(
522             shape=shape,
523             dtype=t.newbyteorder(byteOrder),
524             buffer=data_b,
525             order='F',
526             )
527
528         if version == 1:
529             pass  # No post-data information
530         elif version == 2:
531             # Post-data info:
532             #   * 16 bytes of padding
533             #   * Optional wave note data
534             pad_b = buffer(f.read(16))  # skip the padding
535             assert_null(pad_b, strict=strict)
536             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
537         elif version == 3:
538             # Post-data info:
539             #   * 16 bytes of padding
540             #   * Optional wave note data
541             #   * Optional wave dependency formula
542             """Excerpted from TN003:
543
544             A wave has a dependency formula if it has been bound by a
545             statement such as "wave0 := sin(x)". In this example, the
546             dependency formula is "sin(x)". The formula is stored with
547             no trailing null byte.
548             """
549             pad_b = buffer(f.read(16))  # skip the padding
550             assert_null(pad_b, strict=strict)
551             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
552             bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
553         elif version == 5:
554             # Post-data info:
555             #   * Optional wave dependency formula
556             #   * Optional wave note data
557             #   * Optional extended data units data
558             #   * Optional extended dimension units data
559             #   * Optional dimension label data
560             #   * String indices used for text waves only
561             """Excerpted from TN003:
562
563             dataUnits - Present in versions 1, 2, 3, 5. The dataUnits
564               field stores the units for the data represented by the
565               wave. It is a C string terminated with a null
566               character. This field supports units of 0 to 3 bytes. In
567               version 1, 2 and 3 files, longer units can not be
568               represented. In version 5 files, longer units can be
569               stored using the optional extended data units section of
570               the file.
571
572             xUnits - Present in versions 1, 2, 3. The xUnits field
573               stores the X units for a wave. It is a C string
574               terminated with a null character.  This field supports
575               units of 0 to 3 bytes. In version 1, 2 and 3 files,
576               longer units can not be represented.
577
578             dimUnits - Present in version 5 only. This field is an
579               array of 4 strings, one for each possible wave
580               dimension. Each string supports units of 0 to 3
581               bytes. Longer units can be stored using the optional
582               extended dimension units section of the file.
583             """
584             bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
585             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
586             bin_info['dataEUnits'] = str(f.read(bin_info['dataEUnitsSize'])).strip()
587             bin_info['dimEUnits'] = [
588                 str(f.read(size)).strip() for size in bin_info['dimEUnitsSize']]
589             bin_info['dimLabels'] = []
590             for size in bin_info['dimLabelsSize']:
591                 labels = str(f.read(size)).split(chr(0)) # split null-delimited strings
592                 bin_info['dimLabels'].append([L for L in labels if len(L) > 0])
593             if wave_info['type'] == 0:  # text wave
594                 bin_info['sIndices'] = f.read(bin_info['sIndicesSize'])
595
596     finally:
597         if not hasattr(filename, 'read'):
598             f.close()
599
600     return data, bin_info, wave_info
601
602
603 def saveibw(filename):
604     raise NotImplementedError