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