Fix DynamicLabelsField parsing algorithm.
[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 = 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                 chunks = []
469                 for i in range(size//32):
470                     chunks.append(dim_data[32*i:32*(i+1)])
471                 labels = [b'']
472                 for chunk in chunks:
473                     labels[-1] = labels[-1] + b''.join(chunk)
474                     if b'\x00' in chunk:
475                         labels.append(b'')
476                 labels.pop(-1)
477                 start = end
478             else:
479                 labels = []
480             dim_labels.append(labels)
481         wave_data[self.name] = dim_labels
482
483
484 class DynamicStringIndicesDataField (_DynamicField):
485     """String indices used for text waves only
486     """
487     def pre_pack(self, parents, data):
488         raise NotImplementedError()
489
490     def pre_unpack(self, parents, data):
491         wave_structure = parents[-1]
492         wave_data = self._get_structure_data(parents, data, wave_structure)
493         bin_header = wave_data['bin_header']
494         wave_header = wave_data['wave_header']
495         self.string_indices_size = bin_header['sIndicesSize']
496         self.count = self.string_indices_size // 4
497         if self.count:  # make sure we're in a text wave
498             assert TYPE_TABLE[wave_header['type']] is None, wave_header
499         self.setup()
500
501     def post_unpack(self, parents, data):
502         if not self.count:
503             return
504         wave_structure = parents[-1]
505         wave_data = self._get_structure_data(parents, data, wave_structure)
506         wave_header = wave_data['wave_header']
507         wdata = wave_data['wData']
508         strings = []
509         start = 0
510         for i,offset in enumerate(wave_data['sIndices']):
511             if offset > start:
512                 chars = wdata[start:offset]
513                 strings.append(b''.join(chars))
514                 start = offset
515             elif offset == start:
516                 strings.append(b'')
517             else:
518                 raise ValueError((offset, wave_data['sIndices']))
519         wdata = _numpy.array(strings)
520         shape = [n for n in wave_header['nDim'] if n > 0] or (0,)
521         try:
522             wdata = wdata.reshape(shape)
523         except ValueError:
524             _LOG.error(
525                 'could not reshape strings from {} to {}'.format(
526                     shape, wdata.shape))
527             raise
528         wave_data['wData'] = wdata
529
530
531 class DynamicVersionField (_DynamicField):
532     def pre_pack(self, parents, byte_order):
533         raise NotImplementedError()
534
535     def post_unpack(self, parents, data):
536         wave_structure = parents[-1]
537         wave_data = self._get_structure_data(parents, data, wave_structure)
538         version = wave_data['version']
539         if wave_structure.byte_order in '@=':
540             need_to_reorder_bytes = _need_to_reorder_bytes(version)
541             wave_structure.byte_order = _byte_order(need_to_reorder_bytes)
542             _LOG.debug(
543                 'get byte order from version: {} (reorder? {})'.format(
544                     wave_structure.byte_order, need_to_reorder_bytes))
545         else:
546             need_to_reorder_bytes = False
547
548         old_format = wave_structure.fields[-1].format
549         if version == 1:
550             wave_structure.fields[-1].format = Wave1
551         elif version == 2:
552             wave_structure.fields[-1].format = Wave2
553         elif version == 3:
554             wave_structure.fields[-1].format = Wave3
555         elif version == 5:
556             wave_structure.fields[-1].format = Wave5
557         elif not need_to_reorder_bytes:
558             raise ValueError(
559                 'invalid binary wave version: {}'.format(version))
560
561         if wave_structure.fields[-1].format != old_format:
562             _LOG.debug('change wave headers from {} to {}'.format(
563                     old_format, wave_structure.fields[-1].format))
564             wave_structure.setup()
565         elif need_to_reorder_bytes:
566             wave_structure.setup()
567
568         # we might need to unpack again with the new byte order
569         return need_to_reorder_bytes
570
571
572 class DynamicWaveField (_DynamicField):
573     def post_unpack(self, parents, data):
574         return
575         raise NotImplementedError()  # TODO
576         checksum_size = bin.size + wave.size
577         wave_structure = parents[-1]
578         if version == 5:
579             # Version 5 checksum does not include the wData field.
580             checksum_size -= 4
581         c = _checksum(b, parents[-1].byte_order, 0, checksum_size)
582         if c != 0:
583             raise ValueError(
584                 ('This does not appear to be a valid Igor binary wave file.  '
585                  'Error in checksum: should be 0, is {}.').format(c))
586
587 Wave1 = _DynamicStructure(
588     name='Wave1',
589     fields=[
590         _Field(BinHeader1, 'bin_header', help='Binary wave header'),
591         _Field(WaveHeader2, 'wave_header', help='Wave header'),
592         DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0, array=True),
593         ])
594
595 Wave2 = _DynamicStructure(
596     name='Wave2',
597     fields=[
598         _Field(BinHeader2, 'bin_header', help='Binary wave header'),
599         _Field(WaveHeader2, 'wave_header', help='Wave header'),
600         DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0, array=True),
601         _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16, array=True),
602         DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0, array=True),
603         ])
604
605 Wave3 = _DynamicStructure(
606     name='Wave3',
607     fields=[
608         _Field(BinHeader3, 'bin_header', help='Binary wave header'),
609         _Field(WaveHeader2, 'wave_header', help='Wave header'),
610         DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0, array=True),
611         _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16, array=True),
612         DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0, array=True),
613         DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula', count=0, array=True),
614         ])
615
616 Wave5 = _DynamicStructure(
617     name='Wave5',
618     fields=[
619         _Field(BinHeader5, 'bin_header', help='Binary wave header'),
620         _Field(WaveHeader5, 'wave_header', help='Wave header'),
621         DynamicWaveDataField5('f', 'wData', help='The start of the array of waveform data.', count=0, array=True),
622         DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula.', count=0, array=True),
623         DynamicWaveNoteField('c', 'note', help='Optional wave note data.', count=0, array=True),
624         DynamicDataUnitsField('c', 'data_units', help='Optional extended data units data.', count=0, array=True),
625         DynamicDimensionUnitsField('c', 'dimension_units', help='Optional dimension label data', count=0, array=True),
626         DynamicLabelsField('c', 'labels', help="Optional dimension label data", count=0, array=True),
627         DynamicStringIndicesDataField('P', 'sIndices', help='Dynamic string indices for text waves.', count=0, array=True),
628         ])
629
630 Wave = _DynamicStructure(
631     name='Wave',
632     fields=[
633         DynamicVersionField('h', 'version', help='Version number for backwards compatibility.'),
634         DynamicWaveField(Wave1, 'wave', help='The rest of the wave data.'),
635         ])
636
637
638 def load(filename):
639     if hasattr(filename, 'read'):
640         f = filename  # filename is actually a stream object
641     else:
642         f = open(filename, 'rb')
643     try:
644         Wave.byte_order = '='
645         Wave.setup()
646         data = Wave.unpack_stream(f)
647     finally:
648         if not hasattr(filename, 'read'):
649             f.close()
650
651     return data
652
653
654 def save(filename):
655     raise NotImplementedError