1 # Copyright (C) 2010-2012 W. Trevor King <wking@tremily.us>
3 # This file is part of igor.
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
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
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/>.
18 "Read IGOR Binary Wave files into Numpy arrays."
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.
28 from __future__ import absolute_import
29 import array as _array
30 import struct as _struct
32 import types as _types
34 import numpy as _numpy
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
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)])
63 class StaticStringField (_DynamicField):
64 _null_terminated = False
65 _array_size_field = None
66 def post_unpack(self, parents, data):
67 wave_structure = parents[-1]
68 wave_data = self._get_structure_data(parents, data, wave_structure)
69 d = self._normalize_string(wave_data[self.name])
70 wave_data[self.name] = d
72 def _normalize_string(self, d):
73 if isinstance(d, bytes):
75 elif hasattr(d, 'tobytes'):
77 elif hasattr(d, 'tostring'): # Python 2 compatibility
81 if self._array_size_field:
84 for count in self.counts:
87 strings.append(d[start:end])
88 if self._null_terminated:
89 strings[-1] = strings[-1].split(b'\x00', 1)[0]
91 elif self._null_terminated:
92 d = d.split(b'\x00', 1)[0]
96 class NullStaticStringField (StaticStringField):
97 _null_terminated = True
100 # Begin IGOR constants and typedefs from IgorBin.h
103 TYPE_TABLE = { # (key: integer flag, value: numpy dtype)
104 0:None, # Text wave, not handled in ReadWave.c
105 1:_numpy.complex, # NT_CMPLX, makes number complex.
106 2:_numpy.float32, # NT_FP32, 32 bit fp numbers.
108 4:_numpy.float64, # NT_FP64, 64 bit fp numbers.
110 8:_numpy.int8, # NT_I8, 8 bit signed integer. Requires Igor Pro
113 0x10:_numpy.int16,# NT_I16, 16 bit integer numbers. Requires Igor
116 0x20:_numpy.int32,# NT_I32, 32 bit integer numbers. Requires Igor
119 # 0x40:None, # NT_UNSIGNED, Makes above signed integers
120 # # unsigned. Requires Igor Pro 3.0 or later.
133 BinHeader1 = _Structure( # `version` field pulled out into Wave
136 _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
137 _Field('h', 'checksum', help='Checksum over this header and the wave header.'),
140 BinHeader2 = _Structure( # `version` field pulled out into Wave
143 _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
144 _Field('l', 'noteSize', help='The size of the note text.'),
145 _Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'),
146 _Field('h', 'checksum', help='Checksum over this header and the wave header.'),
149 BinHeader3 = _Structure( # `version` field pulled out into Wave
152 _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'),
153 _Field('l', 'noteSize', help='The size of the note text.'),
154 _Field('l', 'formulaSize', help='The size of the dependency formula, if any.'),
155 _Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'),
156 _Field('h', 'checksum', help='Checksum over this header and the wave header.'),
159 BinHeader5 = _Structure( # `version` field pulled out into Wave
162 _Field('h', 'checksum', help='Checksum over this header and the wave header.'),
163 _Field('l', 'wfmSize', help='The size of the WaveHeader5 data structure plus the wave data.'),
164 _Field('l', 'formulaSize', help='The size of the dependency formula, if any.'),
165 _Field('l', 'noteSize', help='The size of the note text.'),
166 _Field('l', 'dataEUnitsSize', help='The size of optional extended data units.'),
167 _Field('l', 'dimEUnitsSize', help='The size of optional extended dimension units.', count=MAXDIMS),
168 _Field('l', 'dimLabelsSize', help='The size of optional dimension labels.', count=MAXDIMS),
169 _Field('l', 'sIndicesSize', help='The size of string indicies if this is a text wave.'),
170 _Field('l', 'optionsSize1', default=0, help='Reserved. Write zero. Ignore on read.'),
171 _Field('l', 'optionsSize2', default=0, help='Reserved. Write zero. Ignore on read.'),
176 MAX_WAVE_NAME2 = 18 # Maximum length of wave name in version 1 and 2
177 # files. Does not include the trailing null.
178 MAX_WAVE_NAME5 = 31 # Maximum length of wave name in version 5
179 # files. Does not include the trailing null.
182 # Header to an array of waveform data.
184 # `wData` field pulled out into DynamicWaveDataField1
185 WaveHeader2 = _DynamicStructure(
188 _Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'),
189 _Field('P', 'next', default=0, help='Used in memory only. Write zero. Ignore on read.'),
190 NullStaticStringField('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME2+2),
191 _Field('h', 'whVersion', default=0, help='Write 0. Ignore on read.'),
192 _Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'),
193 _Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'),
194 _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1),
195 _Field('c', 'xUnits', default=0, help='Natural x-axis units go here - null if none.', count=MAX_UNIT_CHARS+1),
196 _Field('l', 'npnts', help='Number of data points in wave.'),
197 _Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
198 _Field('d', 'hsA', help='X value for point p = hsA*p + hsB'),
199 _Field('d', 'hsB', help='X value for point p = hsA*p + hsB'),
200 _Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
201 _Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
202 _Field('h', 'fsValid', help='True if full scale values have meaning.'),
203 _Field('d', 'topFullScale', help='The min full scale value for wave.'), # sic, 'min' should probably be 'max'
204 _Field('d', 'botFullScale', help='The min full scale value for wave.'),
205 _Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
206 _Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'),
207 _Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'),
208 _Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'),
209 _Field('L', 'creationDate', help='DateTime of creation. Not used in version 1 files.'),
210 _Field('c', 'wUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=2),
211 _Field('L', 'modDate', help='DateTime of last modification.'),
212 _Field('P', 'waveNoteH', help='Used in memory only. Write zero. Ignore on read.'),
215 # `sIndices` pointer unset (use Wave5_data['sIndices'] instead). This
216 # field is filled in by DynamicStringIndicesDataField.
217 # `wData` field pulled out into DynamicWaveDataField5
218 WaveHeader5 = _DynamicStructure(
221 _Field('P', 'next', help='link to next wave in linked list.'),
222 _Field('L', 'creationDate', help='DateTime of creation.'),
223 _Field('L', 'modDate', help='DateTime of last modification.'),
224 _Field('l', 'npnts', help='Total number of points (multiply dimensions up to first zero).'),
225 _Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'),
226 _Field('h', 'dLock', default=0, help='Reserved. Write zero. Ignore on read.'),
227 _Field('c', 'whpad1', default=0, help='Reserved. Write zero. Ignore on read.', count=6),
228 _Field('h', 'whVersion', default=1, help='Write 1. Ignore on read.'),
229 NullStaticStringField('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME5+1),
230 _Field('l', 'whpad2', default=0, help='Reserved. Write zero. Ignore on read.'),
231 _Field('P', 'dFolder', default=0, help='Used in memory only. Write zero. Ignore on read.'),
232 # Dimensioning info. [0] == rows, [1] == cols etc
233 _Field('l', 'nDim', help='Number of of items in a dimension -- 0 means no data.', count=MAXDIMS),
234 _Field('d', 'sfA', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS),
235 _Field('d', 'sfB', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS),
237 _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1),
238 _Field('c', 'dimUnits', default=0, help='Natural dimension units go here - null if none.', count=(MAXDIMS, MAX_UNIT_CHARS+1)),
239 _Field('h', 'fsValid', help='TRUE if full scale values have meaning.'),
240 _Field('h', 'whpad3', default=0, help='Reserved. Write zero. Ignore on read.'),
241 _Field('d', 'topFullScale', help='The max and max full scale value for wave'), # sic, probably "max and min"
242 _Field('d', 'botFullScale', help='The max and max full scale value for wave.'), # sic, probably "max and min"
243 _Field('P', 'dataEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
244 _Field('P', 'dimEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS),
245 _Field('P', 'dimLabels', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS),
246 _Field('P', 'waveNoteH', default=0, help='Used in memory only. Write zero. Ignore on read.'),
247 _Field('l', 'whUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=16),
248 # The following stuff is considered private to Igor.
249 _Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
250 _Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
251 _Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'),
252 _Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'),
253 _Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'),
254 _Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'),
255 _Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'),
256 _Field('h', 'whpad4', default=0, help='Reserved. Write zero. Ignore on read.'),
257 _Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'),
258 _Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'),
259 _Field('P', 'sIndices', default=0, help='Used in memory only. Write zero. Ignore on read.'),
263 class DynamicWaveDataField1 (_DynamicField):
264 def pre_pack(self, parents, data):
265 raise NotImplementedError()
267 def pre_unpack(self, parents, data):
268 full_structure = parents[0]
269 wave_structure = parents[-1]
270 wave_header_structure = wave_structure.fields[1].format
271 wave_data = self._get_structure_data(parents, data, wave_structure)
272 version = data['version']
273 bin_header = wave_data['bin_header']
274 wave_header = wave_data['wave_header']
276 self.count = wave_header['npnts']
277 self.data_size = self._get_size(bin_header, wave_header_structure.size)
279 type_ = TYPE_TABLE.get(wave_header['type'], None)
281 self.shape = self._get_shape(bin_header, wave_header)
283 type_ = _numpy.dtype('S1')
284 self.shape = (self.data_size,)
285 # dtype() wrapping to avoid numpy.generic and
286 # getset_descriptor issues with the builtin numpy types
287 # (e.g. int32). It has no effect on our local complex
289 self.dtype = _numpy.dtype(type_).newbyteorder(
290 wave_structure.byte_order)
293 bin_header['formulaSize'] > 0 and
294 self.data_size == 0):
297 Igor Pro 2.00 included support for dependency formulae. If
298 a wave was governed by a dependency formula then the
299 actual wave data was not written to disk for that wave,
300 because on loading the wave Igor could recalculate the
301 data. However,this prevented the wave from being loaded
302 into an experiment other than the original
303 experiment. Consequently, in a version of Igor Pro 3.0x,
304 we changed it so that the wave data was written even if
305 the wave was governed by a dependency formula. When
306 reading a binary wave file, you can detect that the wave
307 file does not contain the wave data by examining the
308 wfmSize, formulaSize and npnts fields. If npnts is greater
309 than zero and formulaSize is greater than zero and
310 the waveDataSize as calculated above is zero, then this is
311 a file governed by a dependency formula that was written
312 without the actual wave data.
315 elif TYPE_TABLE.get(wave_header['type'], None) is not None:
316 assert self.data_size == self.count * self.dtype.itemsize, (
317 self.data_size, self.count, self.dtype.itemsize, self.dtype)
319 assert self.data_size >= 0, (
320 bin_header['wfmSize'], wave_header_structure.size)
322 def _get_size(self, bin_header, wave_header_size):
323 return bin_header['wfmSize'] - wave_header_size - 16
325 def _get_shape(self, bin_header, wave_header):
328 def unpack(self, stream):
329 data_b = stream.read(self.data_size)
331 data = _numpy.ndarray(
339 'could not reshape data from {} to {}'.format(
345 class DynamicWaveDataField5 (DynamicWaveDataField1):
346 "Adds support for multidimensional data."
347 def _get_size(self, bin_header, wave_header_size):
348 return bin_header['wfmSize'] - wave_header_size
350 def _get_shape(self, bin_header, wave_header):
351 return [n for n in wave_header['nDim'] if n > 0] or (0,)
354 # End IGOR constants and typedefs from IgorBin.h
357 class DynamicStringField (StaticStringField):
360 def pre_unpack(self, parents, data):
361 size = self._get_size_data(parents, data)
362 if self._array_size_field:
364 self.count = sum(self.counts)
369 def _get_size_data(self, parents, data):
370 wave_structure = parents[-1]
371 wave_data = self._get_structure_data(parents, data, wave_structure)
372 bin_header = wave_data['bin_header']
373 return bin_header[self._size_field]
376 class DynamicWaveNoteField (DynamicStringField):
377 _size_field = 'noteSize'
380 class DynamicDependencyFormulaField (DynamicStringField):
381 """Optional wave dependency formula
383 Excerpted from TN003:
385 A wave has a dependency formula if it has been bound by a
386 statement such as "wave0 := sin(x)". In this example, the
387 dependency formula is "sin(x)". The formula is stored with
388 no trailing null byte.
390 _size_field = 'formulaSize'
391 # Except when it is stored with a trailing null byte :p. See, for
392 # example, test/data/mac-version3Dependent.ibw.
393 _null_terminated = True
396 class DynamicDataUnitsField (DynamicStringField):
397 """Optional extended data units data
399 Excerpted from TN003:
401 dataUnits - Present in versions 1, 2, 3, 5. The dataUnits field
402 stores the units for the data represented by the wave. It is a C
403 string terminated with a null character. This field supports
404 units of 0 to 3 bytes. In version 1, 2 and 3 files, longer units
405 can not be represented. In version 5 files, longer units can be
406 stored using the optional extended data units section of the
409 _size_field = 'dataEUnitsSize'
412 class DynamicDimensionUnitsField (DynamicStringField):
413 """Optional extended dimension units data
415 Excerpted from TN003:
417 xUnits - Present in versions 1, 2, 3. The xUnits field stores the
418 X units for a wave. It is a C string terminated with a null
419 character. This field supports units of 0 to 3 bytes. In
420 version 1, 2 and 3 files, longer units can not be represented.
422 dimUnits - Present in version 5 only. This field is an array of 4
423 strings, one for each possible wave dimension. Each string
424 supports units of 0 to 3 bytes. Longer units can be stored using
425 the optional extended dimension units section of the file.
427 _size_field = 'dimEUnitsSize'
428 _array_size_field = True
431 class DynamicLabelsField (DynamicStringField):
432 """Optional dimension label data
436 If the wave has dimension labels for dimension d then the
437 dimLabelsSize[d] field of the BinHeader5 structure will be
440 A wave will have dimension labels if a SetDimLabel command has
443 A 3 point 1D wave has 4 dimension labels. The first dimension
444 label is the label for the dimension as a whole. The next three
445 dimension labels are the labels for rows 0, 1, and 2. When Igor
446 writes dimension labels to disk, it writes each dimension label as
447 a C string (null-terminated) in a field of 32 bytes.
449 _size_field = 'dimLabelsSize'
450 _array_size_field = True
452 def post_unpack(self, parents, data):
453 wave_structure = parents[-1]
454 wave_data = self._get_structure_data(parents, data, wave_structure)
455 bin_header = wave_data['bin_header']
456 d = b''.join(wave_data[self.name])
459 for size in bin_header[self._size_field]:
462 dim_data = d[start:end]
463 # split null-delimited strings
464 labels = dim_data.split(b'\x00')
468 dim_labels.append(labels)
469 wave_data[self.name] = dim_labels
472 class DynamicStringIndicesDataField (_DynamicField):
473 """String indices used for text waves only
475 def pre_pack(self, parents, data):
476 raise NotImplementedError()
478 def pre_unpack(self, parents, data):
479 wave_structure = parents[-1]
480 wave_data = self._get_structure_data(parents, data, wave_structure)
481 bin_header = wave_data['bin_header']
482 wave_header = wave_data['wave_header']
483 self.string_indices_size = bin_header['sIndicesSize']
484 self.count = self.string_indices_size // 4
485 if self.count: # make sure we're in a text wave
486 assert TYPE_TABLE[wave_header['type']] is None, wave_header
489 def post_unpack(self, parents, data):
492 wave_structure = parents[-1]
493 wave_data = self._get_structure_data(parents, data, wave_structure)
494 wave_header = wave_data['wave_header']
495 wdata = wave_data['wData']
498 for i,offset in enumerate(wave_data['sIndices']):
500 chars = wdata[start:offset]
501 strings.append(b''.join(chars))
503 elif offset == start:
506 raise ValueError((offset, wave_data['sIndices']))
507 wdata = _numpy.array(strings)
508 shape = [n for n in wave_header['nDim'] if n > 0] or (0,)
510 wdata = wdata.reshape(shape)
513 'could not reshape strings from {} to {}'.format(
516 wave_data['wData'] = wdata
519 class DynamicVersionField (_DynamicField):
520 def pre_pack(self, parents, byte_order):
521 raise NotImplementedError()
523 def post_unpack(self, parents, data):
524 wave_structure = parents[-1]
525 wave_data = self._get_structure_data(parents, data, wave_structure)
526 version = wave_data['version']
527 if wave_structure.byte_order in '@=':
528 need_to_reorder_bytes = _need_to_reorder_bytes(version)
529 wave_structure.byte_order = _byte_order(need_to_reorder_bytes)
531 'get byte order from version: {} (reorder? {})'.format(
532 wave_structure.byte_order, need_to_reorder_bytes))
534 need_to_reorder_bytes = False
536 old_format = wave_structure.fields[-1].format
538 wave_structure.fields[-1].format = Wave1
540 wave_structure.fields[-1].format = Wave2
542 wave_structure.fields[-1].format = Wave3
544 wave_structure.fields[-1].format = Wave5
545 elif not need_to_reorder_bytes:
547 'invalid binary wave version: {}'.format(version))
549 if wave_structure.fields[-1].format != old_format:
550 _LOG.debug('change wave headers from {} to {}'.format(
551 old_format, wave_structure.fields[-1].format))
552 wave_structure.setup()
553 elif need_to_reorder_bytes:
554 wave_structure.setup()
556 # we might need to unpack again with the new byte order
557 return need_to_reorder_bytes
560 class DynamicWaveField (_DynamicField):
561 def post_unpack(self, parents, data):
563 raise NotImplementedError() # TODO
564 checksum_size = bin.size + wave.size
565 wave_structure = parents[-1]
567 # Version 5 checksum does not include the wData field.
569 c = _checksum(b, parents[-1].byte_order, 0, checksum_size)
572 ('This does not appear to be a valid Igor binary wave file. '
573 'Error in checksum: should be 0, is {}.').format(c))
575 Wave1 = _DynamicStructure(
578 _Field(BinHeader1, 'bin_header', help='Binary wave header'),
579 _Field(WaveHeader2, 'wave_header', help='Wave header'),
580 DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0),
583 Wave2 = _DynamicStructure(
586 _Field(BinHeader2, 'bin_header', help='Binary wave header'),
587 _Field(WaveHeader2, 'wave_header', help='Wave header'),
588 DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0),
589 _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16),
590 DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0),
593 Wave3 = _DynamicStructure(
596 _Field(BinHeader3, 'bin_header', help='Binary wave header'),
597 _Field(WaveHeader2, 'wave_header', help='Wave header'),
598 DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0),
599 _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16),
600 DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0),
601 DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula', count=0),
604 Wave5 = _DynamicStructure(
607 _Field(BinHeader5, 'bin_header', help='Binary wave header'),
608 _Field(WaveHeader5, 'wave_header', help='Wave header'),
609 DynamicWaveDataField5('f', 'wData', help='The start of the array of waveform data.', count=0),
610 DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula.', count=0),
611 DynamicWaveNoteField('c', 'note', help='Optional wave note data.', count=0),
612 DynamicDataUnitsField('c', 'data_units', help='Optional extended data units data.', count=0),
613 DynamicDimensionUnitsField('c', 'dimension_units', help='Optional dimension label data', count=0),
614 DynamicLabelsField('c', 'labels', help="Optional dimension label data", count=0),
615 DynamicStringIndicesDataField('P', 'sIndices', help='Dynamic string indices for text waves.', count=0),
618 Wave = _DynamicStructure(
621 DynamicVersionField('h', 'version', help='Version number for backwards compatibility.'),
622 DynamicWaveField(Wave1, 'wave', help='The rest of the wave data.'),
627 if hasattr(filename, 'read'):
628 f = filename # filename is actually a stream object
630 f = open(filename, 'rb')
632 Wave.byte_order = '='
634 data = Wave.unpack_stream(f)
636 if not hasattr(filename, 'read'):
643 raise NotImplementedError