Add support for text waves.
[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 _buffer = buffer  # save builtin buffer for clobbered situations
38
39
40 class Field (object):
41     """Represent a Structure field.
42
43     See Also
44     --------
45     Structure
46     """
47     def __init__(self, format, name, default=None, help=None, count=1):
48         self.format = format # See the struct documentation
49         self.name = name
50         self.default = None
51         self.help = help
52         self.count = count
53         self.total_count = numpy.prod(count)
54
55 class Structure (struct.Struct):
56     """Represent a C structure.
57
58     A convenient wrapper around struct.Struct that uses Fields and
59     adds dict-handling methods for transparent name assignment.
60
61     See Also
62     --------
63     Field
64
65     Examples
66     --------
67
68     Represent the C structure::
69
70         struct thing {
71           short version;
72           long size[3];
73         }
74
75     As
76
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)
83     >>> pprint(d)
84     {'size': array([ 33752069, 101124105, 168496141]), 'version': 1}
85     >>> [hex(x) for x in d['size']]
86     ['0x2030405L', '0x6070809L', '0xa0b0c0dL']
87
88     You can even get fancy with multi-dimensional arrays.
89
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)
95     >>> d['size'].shape
96     (3, 2)
97     >>> pprint(d)
98     {'size': array([[ 33752069, 101124105],
99            [168496141, 235868177],
100            [303240213, 370612249]]),
101      'version': 1}
102     """
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
106         self.name = name
107         self.fields = fields
108         self.set_byte_order(byte_order)
109
110     def __str__(self):
111         return self.name
112
113     def set_byte_order(self, byte_order):
114         """Allow changing the format byte_order on the fly.
115         """
116         if (hasattr(self, 'format') and self.format != None
117             and self.format.startswith(byte_order)):
118             return  # no need to change anything
119         format = []
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'))
123
124     def _flatten_args(self, args):
125         # handle Field.count > 0
126         flat_args = []
127         for a,f in zip(args, self.fields):
128             if f.total_count > 1:
129                 flat_args.extend(a)
130             else:
131                 flat_args.append(a)
132         return flat_args
133
134     def _unflatten_args(self, args):
135         # handle Field.count > 0
136         unflat_args = []
137         i = 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)
143             else:
144                 unflat_args.append(args[i])
145             i += f.total_count
146         return unflat_args
147         
148     def pack(self, *args):
149         return struct.Struct.pack(self, *self._flatten_args(args))
150
151     def pack_into(self, buffer, offset, *args):
152         return struct.Struct.pack_into(self, buffer, offset,
153                                        *self._flatten_args(args))
154
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
160                 else:
161                     raise ValueError('{} field not set for {}'.format(
162                             f.name, self.__class__.__name__))
163         return dict
164
165     def pack_dict(self, dict):
166         dict = self._clean_dict(dict)
167         return self.pack(*[dict[f.name] for f in self.fields])
168
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])
173
174     def unpack(self, string):
175         return self._unflatten_args(struct.Struct.unpack(self, string))
176
177     def unpack_from(self, buffer, offset=0):
178         try:
179             args = struct.Struct.unpack_from(self, buffer, offset)
180         except struct.error as e:
181             if not self.name in ('WaveHeader2', 'WaveHeader5'):
182                 raise
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
186             # zero.
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],
193                             unpacked))
194             assert data['npnts'] == 0, data['npnts']
195         return self._unflatten_args(args)
196
197     def unpack_dict(self, string):
198         return dict(zip([f.name for f in self.fields],
199                         self.unpack(string)))
200
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)))
204
205
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)])
218
219
220 # Begin IGOR constants and typedefs from IgorBin.h
221
222 # From IgorMath.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.
227     3:numpy.complex64,
228     4:numpy.float64, # NT_FP64, 64 bit fp numbers.
229     5:numpy.complex128,
230     8:numpy.int8,    # NT_I8, 8 bit signed integer. Requires Igor Pro
231                      # 2.0 or later.
232     9:complexInt8,
233     0x10:numpy.int16,# NT_I16, 16 bit integer numbers. Requires Igor
234                      # Pro 2.0 or later.
235     0x11:complexInt16,
236     0x20:numpy.int32,# NT_I32, 32 bit integer numbers. Requires Igor
237                      # Pro 2.0 or later.
238     0x21:complexInt32,
239 #   0x40:None,       # NT_UNSIGNED, Makes above signed integers
240 #                    # unsigned. Requires Igor Pro 3.0 or later.
241     0x48:numpy.uint8,
242     0x49:complexUInt8,
243     0x50:numpy.uint16,
244     0x51:complexUInt16,
245     0x60:numpy.uint32,
246     0x61:complexUInt32,
247 }
248
249 # From wave.h
250 MAXDIMS = 4
251
252 # From binary.h
253 BinHeaderCommon = Structure(  # WTK: this one is mine.
254     name='BinHeaderCommon',
255     fields=[
256         Field('h', 'version', help='Version number for backwards compatibility.'),
257         ])
258
259 BinHeader1 = Structure(
260     name='BinHeader1',
261     fields=[
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.'),
265         ])
266
267 BinHeader2 = Structure(
268     name='BinHeader2',
269     fields=[
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.'),
275         ])
276
277 BinHeader3 = Structure(
278     name='BinHeader3',
279     fields=[
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.'),
286         ])
287
288 BinHeader5 = Structure(
289     name='BinHeader5',
290     fields=[
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.'),
302         ])
303
304
305 # From wave.h
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.
310 MAX_UNIT_CHARS = 3
311
312 # Header to an array of waveform data.
313
314 WaveHeader2 = Structure(
315     name='WaveHeader2',
316     fields=[
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),
343         ])
344
345 WaveHeader5 = Structure(
346     name='WaveHeader5',
347     fields=[
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),
363         # SI units
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),
388         ])
389
390 # End IGOR constants and typedefs from IgorBin.h
391
392 # Begin functions from ReadWave.c
393
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
398     # reordered.
399     return version & 0xFF == 0
400
401 def byte_order(needToReorderBytes):
402     little_endian = sys.byteorder == 'little'
403     if needToReorderBytes:
404         little_endian = not little_endian
405     if little_endian:
406         return '<'  # little-endian
407     return '>'  # big-endian    
408
409 def version_structs(version, byte_order):
410     if version == 1:
411         bin = BinHeader1
412         wave = WaveHeader2
413     elif version == 2:
414         bin = BinHeader2
415         wave = WaveHeader2
416     elif version == 3:
417         bin = BinHeader3
418         wave = WaveHeader2
419     elif version == 5:
420         bin = BinHeader5
421         wave = WaveHeader5
422     else:
423         raise ValueError(
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
427     if version == 5:
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)
432
433 def checksum(buffer, byte_order, oldcksum, numbytes):
434     x = numpy.ndarray(
435         (numbytes/2,), # 2 bytes to a short -- ignore trailing odd byte
436         dtype=numpy.dtype(byte_order+'h'),
437         buffer=buffer)
438     oldcksum += x.sum()
439     if oldcksum > 2**31:  # fake the C implementation's int rollover
440         oldcksum %= 2**32
441         if oldcksum > 2**31:
442             oldcksum -= 2**31
443     return oldcksum & 0xffff
444
445 def hex_bytes(buffer, spaces=None):
446     r"""Pretty-printing for binary buffers.
447
448     >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'))
449     '0001020304'
450     >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=1)
451     '00 01 02 03 04'
452     >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=2)
453     '0001 0203 04'
454     >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=2)
455     '0001 0203 0405 06'
456     >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=3)
457     '000102 030405 06'
458     """
459     hex_bytes = ['{:02x}'.format(ord(x)) for x in buffer]
460     if spaces is None:
461         return ''.join(hex_bytes)
462     elif spaces is 1:
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)
467
468 def assert_null(buffer, strict=True):
469     r"""Ensure an input buffer is entirely zero.
470
471     >>> assert_null(buffer(''))
472     >>> assert_null(buffer('\x00\x00'))
473     >>> assert_null(buffer('\x00\x01\x02\x03'))
474     Traceback (most recent call last):
475       ...
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
482     """
483     if buffer and ord(max(buffer)) != 0:
484         hex_string = hex_bytes(buffer, spaces=1)
485         if strict:
486             raise ValueError(hex_string)
487         else:
488             sys.stderr.write(
489                 'warning: post-data padding not zero: {}\n'.format(hex_string))
490
491 # Translated from ReadWave()
492 def loadibw(filename, strict=True):
493     if hasattr(filename, 'read'):
494         f = filename  # filename is actually a stream object
495     else:
496         f = open(filename, 'rb')
497     try:
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)
503         
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)
508
509         b = buffer(b + f.read(bin_struct.size + wave_struct.size - BinHeaderCommon.size))
510         c = checksum(b, byteOrder, 0, checkSumSize)
511         if c != 0:
512             raise ValueError(
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 version in [1,2,3]:
518             tail = 16  # 16 = size of wData field in WaveHeader2 structure
519             waveDataSize = bin_info['wfmSize'] - wave_struct.size
520             # =  bin_info['wfmSize']-16 - (wave_struct.size - tail)
521         else:
522             assert version == 5, version
523             tail = 4  # 4 = size of wData field in WaveHeader5 structure
524             waveDataSize = bin_info['wfmSize'] - (wave_struct.size - tail)
525         # dtype() wrapping to avoid numpy.generic and
526         # getset_descriptor issues with the builtin Numpy types
527         # (e.g. int32).  It has no effect on our local complex
528         # integers.
529         if version == 5:
530             shape = [n for n in wave_info['nDim'] if n > 0] or (0,)
531         else:
532             shape = (wave_info['npnts'],)
533         if wave_info['type'] == 0:  # text wave
534             shape = (waveDataSize,)
535             t = numpy.dtype(numpy.int8)
536         else:
537             t = numpy.dtype(TYPE_TABLE[wave_info['type']])
538             assert waveDataSize == wave_info['npnts'] * t.itemsize, (
539                 '{}, {}, {}, {}'.format(
540                     waveDataSize, wave_info['npnts'], t.itemsize, t))
541         if wave_info['npnts'] == 0:
542             data_b = buffer('')
543         else:
544             tail_data = array.array('f', b[-tail:])
545             data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail))
546         data = numpy.ndarray(
547             shape=shape,
548             dtype=t.newbyteorder(byteOrder),
549             buffer=data_b,
550             order='F',
551             )
552
553         if version == 1:
554             pass  # No post-data information
555         elif version == 2:
556             # Post-data info:
557             #   * 16 bytes of padding
558             #   * Optional wave note data
559             pad_b = buffer(f.read(16))  # skip the padding
560             assert_null(pad_b, strict=strict)
561             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
562         elif version == 3:
563             # Post-data info:
564             #   * 16 bytes of padding
565             #   * Optional wave note data
566             #   * Optional wave dependency formula
567             """Excerpted from TN003:
568
569             A wave has a dependency formula if it has been bound by a
570             statement such as "wave0 := sin(x)". In this example, the
571             dependency formula is "sin(x)". The formula is stored with
572             no trailing null byte.
573             """
574             pad_b = buffer(f.read(16))  # skip the padding
575             assert_null(pad_b, strict=strict)
576             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
577             bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
578         elif version == 5:
579             # Post-data info:
580             #   * Optional wave dependency formula
581             #   * Optional wave note data
582             #   * Optional extended data units data
583             #   * Optional extended dimension units data
584             #   * Optional dimension label data
585             #   * String indices used for text waves only
586             """Excerpted from TN003:
587
588             dataUnits - Present in versions 1, 2, 3, 5. The dataUnits
589               field stores the units for the data represented by the
590               wave. It is a C string terminated with a null
591               character. This field supports units of 0 to 3 bytes. In
592               version 1, 2 and 3 files, longer units can not be
593               represented. In version 5 files, longer units can be
594               stored using the optional extended data units section of
595               the file.
596
597             xUnits - Present in versions 1, 2, 3. The xUnits field
598               stores the X units for a wave. It is a C string
599               terminated with a null character.  This field supports
600               units of 0 to 3 bytes. In version 1, 2 and 3 files,
601               longer units can not be represented.
602
603             dimUnits - Present in version 5 only. This field is an
604               array of 4 strings, one for each possible wave
605               dimension. Each string supports units of 0 to 3
606               bytes. Longer units can be stored using the optional
607               extended dimension units section of the file.
608             """
609             bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip()
610             bin_info['note'] = str(f.read(bin_info['noteSize'])).strip()
611             bin_info['dataEUnits'] = str(f.read(bin_info['dataEUnitsSize'])).strip()
612             bin_info['dimEUnits'] = [
613                 str(f.read(size)).strip() for size in bin_info['dimEUnitsSize']]
614             bin_info['dimLabels'] = []
615             for size in bin_info['dimLabelsSize']:
616                 labels = str(f.read(size)).split(chr(0)) # split null-delimited strings
617                 bin_info['dimLabels'].append([L for L in labels if len(L) > 0])
618             if wave_info['type'] == 0:  # text wave
619                 bin_info['sIndices'] = f.read(bin_info['sIndicesSize'])
620
621         if wave_info['type'] == 0:  # text wave
622             # use sIndices to split data into strings
623             strings = []
624             start = 0
625             for i,string_index in enumerate(bin_info['sIndices']):
626                 offset = ord(string_index)
627                 if offset > start:
628                     string = data[start:offset]
629                     strings.append(''.join(chr(x) for x in string))
630                     start = offset
631                 else:
632                     assert offset == 0, offset
633             data = numpy.array(strings)
634             shape = [n for n in wave_info['nDim'] if n > 0] or (0,)
635             data.reshape(shape)
636     finally:
637         if not hasattr(filename, 'read'):
638             f.close()
639
640     return data, bin_info, wave_info
641
642
643 def saveibw(filename):
644     raise NotImplementedError