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