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