Fixes to string/byte handling for Python 3 compatibility.
[igor.git] / igor / binarywave.py
1 # Copyright (C) 2010-2012 W. Trevor King <wking@tremily.us>
2 #
3 # This file is part of igor.
4 #
5 # igor is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU Lesser General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option) any
8 # later version.
9 #
10 # igor is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
13 # details.
14 #
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with igor.  If not, see <http://www.gnu.org/licenses/>.
17
18 "Read IGOR Binary Wave files into Numpy arrays."
19
20 # Based on WaveMetric's Technical Note 003, "Igor Binary Format"
21 #   ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN003.zip
22 # From ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN000.txt
23 #   We place no restrictions on copying Technical Notes, with the
24 #   exception that you cannot resell them. So read, enjoy, and
25 #   share. We hope IGOR Technical Notes will provide you with lots of
26 #   valuable information while you are developing IGOR applications.
27
28 from __future__ import absolute_import
29 import array as _array
30 import struct as _struct
31 import sys as _sys
32 import types as _types
33
34 import numpy as _numpy
35
36 from . import LOG as _LOG
37 from .struct import Structure as _Structure
38 from .struct import DynamicStructure as _DynamicStructure
39 from .struct import Field as _Field
40 from .struct import DynamicField as _DynamicField
41 from .util import assert_null as _assert_null
42 from .util import byte_order as _byte_order
43 from .util import need_to_reorder_bytes as _need_to_reorder_bytes
44 from .util import checksum as _checksum
45
46
47 # Numpy doesn't support complex integers by default, see
48 #   http://mail.python.org/pipermail/python-dev/2002-April/022408.html
49 #   http://mail.scipy.org/pipermail/numpy-discussion/2007-October/029447.html
50 # So we roll our own types.  See
51 #   http://docs.scipy.org/doc/numpy/user/basics.rec.html
52 #   http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html
53 complexInt8 = _numpy.dtype([('real', _numpy.int8), ('imag', _numpy.int8)])
54 complexInt16 = _numpy.dtype([('real', _numpy.int16), ('imag', _numpy.int16)])
55 complexInt32 = _numpy.dtype([('real', _numpy.int32), ('imag', _numpy.int32)])
56 complexUInt8 = _numpy.dtype([('real', _numpy.uint8), ('imag', _numpy.uint8)])
57 complexUInt16 = _numpy.dtype(
58     [('real', _numpy.uint16), ('imag', _numpy.uint16)])
59 complexUInt32 = _numpy.dtype(
60     [('real', _numpy.uint32), ('imag', _numpy.uint32)])
61
62
63 class StaticStringField (_DynamicField):
64     _null_terminated = False
65     _array_size_field = None
66     def post_unpack(self, parents, data):
67         wave_structure = parents[-1]
68         wave_data = self._get_structure_data(parents, data, wave_structure)
69         d = self._normalize_string(wave_data[self.name])
70         wave_data[self.name] = d
71
72     def _normalize_string(self, d):
73         if isinstance(d, bytes):
74             pass
75         elif hasattr(d, 'tobytes'):
76             d = d.tobytes()
77         elif hasattr(d, 'tostring'):  # Python 2 compatibility
78             d = d.tostring()
79         else:
80             d = b''.join(d)
81         if self._array_size_field:
82             start = 0
83             strings = []
84             for count in self.counts:
85                 end = start + count
86                 if end > start:
87                     strings.append(d[start:end])
88                     if self._null_terminated:
89                         strings[-1] = strings[-1].split(b'\x00', 1)[0]
90                     start = end
91         elif self._null_terminated:
92             d = d.split(b'\x00', 1)[0]
93         return d
94
95
96 class NullStaticStringField (StaticStringField):
97     _null_terminated = True
98
99
100 # Begin IGOR constants and typedefs from IgorBin.h
101
102 # From IgorMath.h
103 TYPE_TABLE = {        # (key: integer flag, value: numpy dtype)
104     0:None,           # Text wave, not handled in ReadWave.c
105     1:_numpy.complex, # NT_CMPLX, makes number complex.
106     2:_numpy.float32, # NT_FP32, 32 bit fp numbers.
107     3:_numpy.complex64,
108     4:_numpy.float64, # NT_FP64, 64 bit fp numbers.
109     5:_numpy.complex128,
110     8:_numpy.int8,    # NT_I8, 8 bit signed integer. Requires Igor Pro
111                       # 2.0 or later.
112     9:complexInt8,
113     0x10:_numpy.int16,# NT_I16, 16 bit integer numbers. Requires Igor
114                       # Pro 2.0 or later.
115     0x11:complexInt16,
116     0x20:_numpy.int32,# NT_I32, 32 bit integer numbers. Requires Igor
117                       # Pro 2.0 or later.
118     0x21:complexInt32,
119 #   0x40:None,        # NT_UNSIGNED, Makes above signed integers
120 #                     # unsigned. Requires Igor Pro 3.0 or later.
121     0x48:_numpy.uint8,
122     0x49:complexUInt8,
123     0x50:_numpy.uint16,
124     0x51:complexUInt16,
125     0x60:_numpy.uint32,
126     0x61:complexUInt32,
127 }
128
129 # From wave.h
130 MAXDIMS = 4
131
132 # From binary.h
133 BinHeader1 = _Structure(  # `version` field pulled out into Wave
134     name='BinHeader1',
135     fields=[
136         _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
137         _Field('h', 'checksum', help='Checksum over this header and the wave header.'),
138         ])
139
140 BinHeader2 = _Structure(  # `version` field pulled out into Wave
141     name='BinHeader2',
142     fields=[
143         _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
144         _Field('l', 'noteSize', help='The size of the note text.'),
145         _Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'),
146         _Field('h', 'checksum', help='Checksum over this header and the wave header.'),
147         ])
148
149 BinHeader3 = _Structure(  # `version` field pulled out into Wave
150     name='BinHeader3',
151     fields=[
152         _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
153         _Field('l', 'noteSize', help='The size of the note text.'),
154         _Field('l', 'formulaSize', help='The size of the dependency formula, if any.'),
155         _Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'),
156         _Field('h', 'checksum', help='Checksum over this header and the wave header.'),
157         ])
158
159 BinHeader5 = _Structure(  # `version` field pulled out into Wave
160     name='BinHeader5',
161     fields=[
162         _Field('h', 'checksum', help='Checksum over this header and the wave header.'),
163         _Field('l', 'wfmSize', help='The size of the WaveHeader5 data structure plus the wave data.'),
164         _Field('l', 'formulaSize', help='The size of the dependency formula, if any.'),
165         _Field('l', 'noteSize', help='The size of the note text.'),
166         _Field('l', 'dataEUnitsSize', help='The size of optional extended data units.'),
167         _Field('l', 'dimEUnitsSize', help='The size of optional extended dimension units.', count=MAXDIMS),
168         _Field('l', 'dimLabelsSize', help='The size of optional dimension labels.', count=MAXDIMS),
169         _Field('l', 'sIndicesSize', help='The size of string indicies if this is a text wave.'),
170         _Field('l', 'optionsSize1', default=0, help='Reserved. Write zero. Ignore on read.'),
171         _Field('l', 'optionsSize2', default=0, help='Reserved. Write zero. Ignore on read.'),
172         ])
173
174
175 # From wave.h
176 MAX_WAVE_NAME2 = 18 # Maximum length of wave name in version 1 and 2
177                     # files. Does not include the trailing null.
178 MAX_WAVE_NAME5 = 31 # Maximum length of wave name in version 5
179                     # files. Does not include the trailing null.
180 MAX_UNIT_CHARS = 3
181
182 # Header to an array of waveform data.
183
184 # `wData` field pulled out into DynamicWaveDataField1
185 WaveHeader2 = _DynamicStructure(
186     name='WaveHeader2',
187     fields=[
188         _Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'),
189         _Field('P', 'next', default=0, help='Used in memory only. Write zero. Ignore on read.'),
190         NullStaticStringField('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME2+2),
191         _Field('h', 'whVersion', default=0, help='Write 0. Ignore on read.'),
192         _Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'),
193         _Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'),
194         _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1),
195         _Field('c', 'xUnits', default=0, help='Natural x-axis units go here - null if none.', count=MAX_UNIT_CHARS+1),
196         _Field('l', 'npnts', help='Number of data points in wave.'),
197         _Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
198         _Field('d', 'hsA', help='X value for point p = hsA*p + hsB'),
199         _Field('d', 'hsB', help='X value for point p = hsA*p + hsB'),
200         _Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
201         _Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
202         _Field('h', 'fsValid', help='True if full scale values have meaning.'),
203         _Field('d', 'topFullScale', help='The min full scale value for wave.'), # sic, 'min' should probably be 'max'
204         _Field('d', 'botFullScale', help='The min full scale value for wave.'),
205         _Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
206         _Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'),
207         _Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'),
208         _Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'),
209         _Field('L', 'creationDate', help='DateTime of creation.  Not used in version 1 files.'),
210         _Field('c', 'wUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=2),
211         _Field('L', 'modDate', help='DateTime of last modification.'),
212         _Field('P', 'waveNoteH', help='Used in memory only. Write zero. Ignore on read.'),
213         ])
214
215 # `sIndices` pointer unset (use Wave5_data['sIndices'] instead).  This
216 # field is filled in by DynamicStringIndicesDataField.
217 # `wData` field pulled out into DynamicWaveDataField5
218 WaveHeader5 = _DynamicStructure(
219     name='WaveHeader5',
220     fields=[
221         _Field('P', 'next', help='link to next wave in linked list.'),
222         _Field('L', 'creationDate', help='DateTime of creation.'),
223         _Field('L', 'modDate', help='DateTime of last modification.'),
224         _Field('l', 'npnts', help='Total number of points (multiply dimensions up to first zero).'),
225         _Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'),
226         _Field('h', 'dLock', default=0, help='Reserved. Write zero. Ignore on read.'),
227         _Field('c', 'whpad1', default=0, help='Reserved. Write zero. Ignore on read.', count=6),
228         _Field('h', 'whVersion', default=1, help='Write 1. Ignore on read.'),
229         NullStaticStringField('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME5+1),
230         _Field('l', 'whpad2', default=0, help='Reserved. Write zero. Ignore on read.'),
231         _Field('P', 'dFolder', default=0, help='Used in memory only. Write zero. Ignore on read.'),
232         # Dimensioning info. [0] == rows, [1] == cols etc
233         _Field('l', 'nDim', help='Number of of items in a dimension -- 0 means no data.', count=MAXDIMS),
234         _Field('d', 'sfA', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS),
235         _Field('d', 'sfB', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS),
236         # SI units
237         _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1),
238         _Field('c', 'dimUnits', default=0, help='Natural dimension units go here - null if none.', count=(MAXDIMS, MAX_UNIT_CHARS+1)),
239         _Field('h', 'fsValid', help='TRUE if full scale values have meaning.'),
240         _Field('h', 'whpad3', default=0, help='Reserved. Write zero. Ignore on read.'),
241         _Field('d', 'topFullScale', help='The max and max full scale value for wave'), # sic, probably "max and min"
242         _Field('d', 'botFullScale', help='The max and max full scale value for wave.'), # sic, probably "max and min"
243         _Field('P', 'dataEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
244         _Field('P', 'dimEUnits', default=0, help='Used in memory only. Write zero.  Ignore on read.', count=MAXDIMS),
245         _Field('P', 'dimLabels', default=0, help='Used in memory only. Write zero.  Ignore on read.', count=MAXDIMS),
246         _Field('P', 'waveNoteH', default=0, help='Used in memory only. Write zero. Ignore on read.'),
247         _Field('l', 'whUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=16),
248         # The following stuff is considered private to Igor.
249         _Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
250         _Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
251         _Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
252         _Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
253         _Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'),
254         _Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'),
255         _Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'),
256         _Field('h', 'whpad4', default=0, help='Reserved. Write zero. Ignore on read.'),
257         _Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'),
258         _Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'),
259         _Field('P', 'sIndices', default=0, help='Used in memory only. Write zero. Ignore on read.'),
260         ])
261
262
263 class DynamicWaveDataField1 (_DynamicField):
264     def pre_pack(self, parents, data):
265         raise NotImplementedError()
266
267     def pre_unpack(self, parents, data):
268         full_structure = parents[0]
269         wave_structure = parents[-1]
270         wave_header_structure = wave_structure.fields[1].format
271         wave_data = self._get_structure_data(parents, data, wave_structure)
272         version = data['version']
273         bin_header = wave_data['bin_header']
274         wave_header = wave_data['wave_header']
275
276         self.count = wave_header['npnts']
277         self.data_size = self._get_size(bin_header, wave_header_structure.size)
278
279         type_ = TYPE_TABLE.get(wave_header['type'], None)
280         if type_:
281             self.shape = self._get_shape(bin_header, wave_header)
282         else:  # text wave
283             type_ = _numpy.dtype('S1')
284             self.shape = (self.data_size,)
285         # dtype() wrapping to avoid numpy.generic and
286         # getset_descriptor issues with the builtin numpy types
287         # (e.g. int32).  It has no effect on our local complex
288         # integers.
289         self.dtype = _numpy.dtype(type_).newbyteorder(
290             wave_structure.byte_order)
291         if (version == 3 and
292             self.count > 0 and
293             bin_header['formulaSize'] > 0 and
294             self.data_size == 0):
295             """From TN003:
296
297             Igor Pro 2.00 included support for dependency formulae. If
298             a wave was governed by a dependency formula then the
299             actual wave data was not written to disk for that wave,
300             because on loading the wave Igor could recalculate the
301             data. However,this prevented the wave from being loaded
302             into an experiment other than the original
303             experiment. Consequently, in a version of Igor Pro 3.0x,
304             we changed it so that the wave data was written even if
305             the wave was governed by a dependency formula. When
306             reading a binary wave file, you can detect that the wave
307             file does not contain the wave data by examining the
308             wfmSize, formulaSize and npnts fields. If npnts is greater
309             than zero and formulaSize is greater than zero and
310             the waveDataSize as calculated above is zero, then this is
311             a file governed by a dependency formula that was written
312             without the actual wave data.
313             """
314             self.shape = (0,)
315         elif TYPE_TABLE.get(wave_header['type'], None) is not None:
316             assert self.data_size == self.count * self.dtype.itemsize, (
317                 self.data_size, self.count, self.dtype.itemsize, self.dtype)
318         else:
319             assert self.data_size >= 0, (
320                 bin_header['wfmSize'], wave_header_structure.size)
321
322     def _get_size(self, bin_header, wave_header_size):
323         return bin_header['wfmSize'] - wave_header_size - 16
324
325     def _get_shape(self, bin_header, wave_header):
326         return (self.count,)
327
328     def unpack(self, stream):
329         data_b = stream.read(self.data_size)
330         try:
331             data = _numpy.ndarray(
332                 shape=self.shape,
333                 dtype=self.dtype,
334                 buffer=data_b,
335                 order='F',
336                 )
337         except:
338             _LOG.error(
339                 'could not reshape data from {} to {}'.format(
340                     self.shape, data_b))
341             raise
342         return data
343
344
345 class DynamicWaveDataField5 (DynamicWaveDataField1):
346     "Adds support for multidimensional data."
347     def _get_size(self, bin_header, wave_header_size):
348         return bin_header['wfmSize'] - wave_header_size
349
350     def _get_shape(self, bin_header, wave_header):
351         return [n for n in wave_header['nDim'] if n > 0] or (0,)
352
353
354 # End IGOR constants and typedefs from IgorBin.h
355
356
357 class DynamicStringField (StaticStringField):
358     _size_field = None
359
360     def pre_unpack(self, parents, data):
361         size = self._get_size_data(parents, data)
362         if self._array_size_field:
363             self.counts = size
364             self.count = sum(self.counts)
365         else:
366             self.count = size
367         self.setup()
368
369     def _get_size_data(self, parents, data):
370         wave_structure = parents[-1]
371         wave_data = self._get_structure_data(parents, data, wave_structure)
372         bin_header = wave_data['bin_header']
373         return bin_header[self._size_field]
374
375
376 class DynamicWaveNoteField (DynamicStringField):
377     _size_field = 'noteSize'
378
379
380 class DynamicDependencyFormulaField (DynamicStringField):
381     """Optional wave dependency formula
382
383     Excerpted from TN003:
384
385     A wave has a dependency formula if it has been bound by a
386     statement such as "wave0 := sin(x)". In this example, the
387     dependency formula is "sin(x)". The formula is stored with
388     no trailing null byte.
389     """
390     _size_field = 'formulaSize'
391     # Except when it is stored with a trailing null byte :p.  See, for
392     # example, test/data/mac-version3Dependent.ibw.
393     _null_terminated = True
394
395
396 class DynamicDataUnitsField (DynamicStringField):
397     """Optional extended data units data
398
399     Excerpted from TN003:
400
401     dataUnits - Present in versions 1, 2, 3, 5. The dataUnits field
402       stores the units for the data represented by the wave. It is a C
403       string terminated with a null character. This field supports
404       units of 0 to 3 bytes. In version 1, 2 and 3 files, longer units
405       can not be represented. In version 5 files, longer units can be
406       stored using the optional extended data units section of the
407       file.
408     """
409     _size_field = 'dataEUnitsSize'
410
411
412 class DynamicDimensionUnitsField (DynamicStringField):
413     """Optional extended dimension units data
414
415     Excerpted from TN003:
416
417     xUnits - Present in versions 1, 2, 3. The xUnits field stores the
418       X units for a wave. It is a C string terminated with a null
419       character.  This field supports units of 0 to 3 bytes. In
420       version 1, 2 and 3 files, longer units can not be represented.
421
422     dimUnits - Present in version 5 only. This field is an array of 4
423       strings, one for each possible wave dimension. Each string
424       supports units of 0 to 3 bytes. Longer units can be stored using
425       the optional extended dimension units section of the file.
426     """
427     _size_field = 'dimEUnitsSize'
428     _array_size_field = True
429
430
431 class DynamicLabelsField (DynamicStringField):
432     """Optional dimension label data
433
434     From TN003:
435
436     If the wave has dimension labels for dimension d then the
437     dimLabelsSize[d] field of the BinHeader5 structure will be
438     non-zero.
439
440     A wave will have dimension labels if a SetDimLabel command has
441     been executed on it.
442
443     A 3 point 1D wave has 4 dimension labels. The first dimension
444     label is the label for the dimension as a whole. The next three
445     dimension labels are the labels for rows 0, 1, and 2. When Igor
446     writes dimension labels to disk, it writes each dimension label as
447     a C string (null-terminated) in a field of 32 bytes.
448     """
449     _size_field = 'dimLabelsSize'
450     _array_size_field = True
451
452     def post_unpack(self, parents, data):
453         wave_structure = parents[-1]
454         wave_data = self._get_structure_data(parents, data, wave_structure)
455         bin_header = wave_data['bin_header']
456         d = b''.join(wave_data[self.name])
457         dim_labels = []
458         start = 0
459         for size in bin_header[self._size_field]:
460             end = start + size
461             if end > start:
462                 dim_data = d[start:end]
463                 # split null-delimited strings
464                 labels = dim_data.split(b'\x00')
465                 start = end
466             else:
467                 labels = []
468             dim_labels.append(labels)
469         wave_data[self.name] = dim_labels
470
471
472 class DynamicStringIndicesDataField (_DynamicField):
473     """String indices used for text waves only
474     """
475     def pre_pack(self, parents, data):
476         raise NotImplementedError()
477
478     def pre_unpack(self, parents, data):
479         wave_structure = parents[-1]
480         wave_data = self._get_structure_data(parents, data, wave_structure)
481         bin_header = wave_data['bin_header']
482         wave_header = wave_data['wave_header']
483         self.string_indices_size = bin_header['sIndicesSize']
484         self.count = self.string_indices_size // 4
485         if self.count:  # make sure we're in a text wave
486             assert TYPE_TABLE[wave_header['type']] is None, wave_header
487         self.setup()
488
489     def post_unpack(self, parents, data):
490         if not self.count:
491             return
492         wave_structure = parents[-1]
493         wave_data = self._get_structure_data(parents, data, wave_structure)
494         wave_header = wave_data['wave_header']
495         wdata = wave_data['wData']
496         strings = []
497         start = 0
498         for i,offset in enumerate(wave_data['sIndices']):
499             if offset > start:
500                 chars = wdata[start:offset]
501                 strings.append(b''.join(chars))
502                 start = offset
503             elif offset == start:
504                 strings.append(b'')
505             else:
506                 raise ValueError((offset, wave_data['sIndices']))
507         wdata = _numpy.array(strings)
508         shape = [n for n in wave_header['nDim'] if n > 0] or (0,)
509         try:
510             wdata = wdata.reshape(shape)
511         except ValueError:
512             _LOG.error(
513                 'could not reshape strings from {} to {}'.format(
514                     shape, wdata.shape))
515             raise
516         wave_data['wData'] = wdata
517
518
519 class DynamicVersionField (_DynamicField):
520     def pre_pack(self, parents, byte_order):
521         raise NotImplementedError()
522
523     def post_unpack(self, parents, data):
524         wave_structure = parents[-1]
525         wave_data = self._get_structure_data(parents, data, wave_structure)
526         version = wave_data['version']
527         if wave_structure.byte_order in '@=':
528             need_to_reorder_bytes = _need_to_reorder_bytes(version)
529             wave_structure.byte_order = _byte_order(need_to_reorder_bytes)
530             _LOG.debug(
531                 'get byte order from version: {} (reorder? {})'.format(
532                     wave_structure.byte_order, need_to_reorder_bytes))
533         else:
534             need_to_reorder_bytes = False
535
536         old_format = wave_structure.fields[-1].format
537         if version == 1:
538             wave_structure.fields[-1].format = Wave1
539         elif version == 2:
540             wave_structure.fields[-1].format = Wave2
541         elif version == 3:
542             wave_structure.fields[-1].format = Wave3
543         elif version == 5:
544             wave_structure.fields[-1].format = Wave5
545         elif not need_to_reorder_bytes:
546             raise ValueError(
547                 'invalid binary wave version: {}'.format(version))
548
549         if wave_structure.fields[-1].format != old_format:
550             _LOG.debug('change wave headers from {} to {}'.format(
551                     old_format, wave_structure.fields[-1].format))
552             wave_structure.setup()
553         elif need_to_reorder_bytes:
554             wave_structure.setup()
555
556         # we might need to unpack again with the new byte order
557         return need_to_reorder_bytes
558
559
560 class DynamicWaveField (_DynamicField):
561     def post_unpack(self, parents, data):
562         return
563         raise NotImplementedError()  # TODO
564         checksum_size = bin.size + wave.size
565         wave_structure = parents[-1]
566         if version == 5:
567             # Version 5 checksum does not include the wData field.
568             checksum_size -= 4
569         c = _checksum(b, parents[-1].byte_order, 0, checksum_size)
570         if c != 0:
571             raise ValueError(
572                 ('This does not appear to be a valid Igor binary wave file.  '
573                  'Error in checksum: should be 0, is {}.').format(c))
574
575 Wave1 = _DynamicStructure(
576     name='Wave1',
577     fields=[
578         _Field(BinHeader1, 'bin_header', help='Binary wave header'),
579         _Field(WaveHeader2, 'wave_header', help='Wave header'),
580         DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0),
581         ])
582
583 Wave2 = _DynamicStructure(
584     name='Wave2',
585     fields=[
586         _Field(BinHeader2, 'bin_header', help='Binary wave header'),
587         _Field(WaveHeader2, 'wave_header', help='Wave header'),
588         DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0),
589         _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16),
590         DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0),
591         ])
592
593 Wave3 = _DynamicStructure(
594     name='Wave3',
595     fields=[
596         _Field(BinHeader3, 'bin_header', help='Binary wave header'),
597         _Field(WaveHeader2, 'wave_header', help='Wave header'),
598         DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0),
599         _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16),
600         DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0),
601         DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula', count=0),
602         ])
603
604 Wave5 = _DynamicStructure(
605     name='Wave5',
606     fields=[
607         _Field(BinHeader5, 'bin_header', help='Binary wave header'),
608         _Field(WaveHeader5, 'wave_header', help='Wave header'),
609         DynamicWaveDataField5('f', 'wData', help='The start of the array of waveform data.', count=0),
610         DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula.', count=0),
611         DynamicWaveNoteField('c', 'note', help='Optional wave note data.', count=0),
612         DynamicDataUnitsField('c', 'data_units', help='Optional extended data units data.', count=0),
613         DynamicDimensionUnitsField('c', 'dimension_units', help='Optional dimension label data', count=0),
614         DynamicLabelsField('c', 'labels', help="Optional dimension label data", count=0),
615         DynamicStringIndicesDataField('P', 'sIndices', help='Dynamic string indices for text waves.', count=0),
616         ])
617
618 Wave = _DynamicStructure(
619     name='Wave',
620     fields=[
621         DynamicVersionField('h', 'version', help='Version number for backwards compatibility.'),
622         DynamicWaveField(Wave1, 'wave', help='The rest of the wave data.'),
623         ])
624
625
626 def load(filename):
627     if hasattr(filename, 'read'):
628         f = filename  # filename is actually a stream object
629     else:
630         f = open(filename, 'rb')
631     try:
632         Wave.byte_order = '='
633         Wave.setup()
634         data = Wave.unpack_stream(f)
635     finally:
636         if not hasattr(filename, 'read'):
637             f.close()
638
639     return data
640
641
642 def save(filename):
643     raise NotImplementedError