+ self.dtype = _numpy.dtype(type_).newbyteorder(
+ wave_structure.byte_order)
+ if (version == 3 and
+ self.count > 0 and
+ bin_header['formulaSize'] > 0 and
+ self.data_size == 0):
+ """From TN003:
+
+ Igor Pro 2.00 included support for dependency formulae. If
+ a wave was governed by a dependency formula then the
+ actual wave data was not written to disk for that wave,
+ because on loading the wave Igor could recalculate the
+ data. However,this prevented the wave from being loaded
+ into an experiment other than the original
+ experiment. Consequently, in a version of Igor Pro 3.0x,
+ we changed it so that the wave data was written even if
+ the wave was governed by a dependency formula. When
+ reading a binary wave file, you can detect that the wave
+ file does not contain the wave data by examining the
+ wfmSize, formulaSize and npnts fields. If npnts is greater
+ than zero and formulaSize is greater than zero and
+ the waveDataSize as calculated above is zero, then this is
+ a file governed by a dependency formula that was written
+ without the actual wave data.
+ """
+ self.shape = (0,)
+ elif TYPE_TABLE.get(wave_header['type'], None) is not None:
+ assert self.data_size == self.count * self.dtype.itemsize, (
+ self.data_size, self.count, self.dtype.itemsize, self.dtype)
+ else:
+ assert self.data_size >= 0, (
+ bin_header['wfmSize'], wave_header_structure.size)
+
+ def _get_size(self, bin_header, wave_header_size):
+ return bin_header['wfmSize'] - wave_header_size - 16
+
+ def _get_shape(self, bin_header, wave_header):
+ return (self.count,)
+
+ def unpack(self, stream):
+ data_b = stream.read(self.data_size)
+ try:
+ data = _numpy.ndarray(
+ shape=self.shape,
+ dtype=self.dtype,
+ buffer=data_b,
+ order='F',
+ )
+ except:
+ _LOG.error(
+ 'could not reshape data from {} to {}'.format(
+ self.shape, data_b))
+ raise
+ return data
+
+
+class DynamicWaveDataField5 (DynamicWaveDataField1):
+ "Adds support for multidimensional data."
+ def _get_size(self, bin_header, wave_header_size):
+ return bin_header['wfmSize'] - wave_header_size
+
+ def _get_shape(self, bin_header, wave_header):
+ return [n for n in wave_header['nDim'] if n > 0] or (0,)
+
+
+# End IGOR constants and typedefs from IgorBin.h
+
+
+class DynamicStringField (StaticStringField):
+ _size_field = None
+
+ def pre_unpack(self, parents, data):
+ size = self._get_size_data(parents, data)
+ if self._array_size_field:
+ self.counts = size
+ self.count = sum(self.counts)
+ else:
+ self.count = size
+ self.setup()
+
+ def _get_size_data(self, parents, data):
+ wave_structure = parents[-1]
+ wave_data = self._get_structure_data(parents, data, wave_structure)
+ bin_header = wave_data['bin_header']
+ return bin_header[self._size_field]
+
+
+class DynamicWaveNoteField (DynamicStringField):
+ _size_field = 'noteSize'
+
+
+class DynamicDependencyFormulaField (DynamicStringField):
+ """Optional wave dependency formula
+
+ Excerpted from TN003:
+
+ A wave has a dependency formula if it has been bound by a
+ statement such as "wave0 := sin(x)". In this example, the
+ dependency formula is "sin(x)". The formula is stored with
+ no trailing null byte.
+ """
+ _size_field = 'formulaSize'
+ # Except when it is stored with a trailing null byte :p. See, for
+ # example, test/data/mac-version3Dependent.ibw.
+ _null_terminated = True
+
+
+class DynamicDataUnitsField (DynamicStringField):
+ """Optional extended data units data
+
+ Excerpted from TN003:
+
+ dataUnits - Present in versions 1, 2, 3, 5. The dataUnits field
+ stores the units for the data represented by the wave. It is a C
+ string terminated with a null character. This field supports
+ units of 0 to 3 bytes. In version 1, 2 and 3 files, longer units
+ can not be represented. In version 5 files, longer units can be
+ stored using the optional extended data units section of the
+ file.
+ """
+ _size_field = 'dataEUnitsSize'
+
+
+class DynamicDimensionUnitsField (DynamicStringField):
+ """Optional extended dimension units data
+
+ Excerpted from TN003:
+
+ xUnits - Present in versions 1, 2, 3. The xUnits field stores the
+ X units for a wave. It is a C string terminated with a null
+ character. This field supports units of 0 to 3 bytes. In
+ version 1, 2 and 3 files, longer units can not be represented.
+
+ dimUnits - Present in version 5 only. This field is an array of 4
+ strings, one for each possible wave dimension. Each string
+ supports units of 0 to 3 bytes. Longer units can be stored using
+ the optional extended dimension units section of the file.
+ """
+ _size_field = 'dimEUnitsSize'
+ _array_size_field = True
+
+
+class DynamicLabelsField (DynamicStringField):
+ """Optional dimension label data
+
+ From TN003:
+
+ If the wave has dimension labels for dimension d then the
+ dimLabelsSize[d] field of the BinHeader5 structure will be
+ non-zero.
+
+ A wave will have dimension labels if a SetDimLabel command has
+ been executed on it.
+
+ A 3 point 1D wave has 4 dimension labels. The first dimension
+ label is the label for the dimension as a whole. The next three
+ dimension labels are the labels for rows 0, 1, and 2. When Igor
+ writes dimension labels to disk, it writes each dimension label as
+ a C string (null-terminated) in a field of 32 bytes.
+ """
+ _size_field = 'dimLabelsSize'
+ _array_size_field = True
+
+ def post_unpack(self, parents, data):
+ wave_structure = parents[-1]
+ wave_data = self._get_structure_data(parents, data, wave_structure)
+ bin_header = wave_data['bin_header']
+ d = wave_data[self.name]
+ dim_labels = []
+ start = 0
+ for size in bin_header[self._size_field]:
+ end = start + size
+ if end > start:
+ dim_data = d[start:end]
+ chunks = []
+ for i in range(size//32):
+ chunks.append(dim_data[32*i:32*(i+1)])
+ labels = [b'']
+ for chunk in chunks:
+ labels[-1] = labels[-1] + b''.join(chunk)
+ if b'\x00' in chunk:
+ labels.append(b'')
+ labels.pop(-1)
+ start = end
+ else:
+ labels = []
+ dim_labels.append(labels)
+ wave_data[self.name] = dim_labels
+
+
+class DynamicStringIndicesDataField (_DynamicField):
+ """String indices used for text waves only
+ """
+ def pre_pack(self, parents, data):
+ raise NotImplementedError()
+
+ def pre_unpack(self, parents, data):
+ wave_structure = parents[-1]
+ wave_data = self._get_structure_data(parents, data, wave_structure)
+ bin_header = wave_data['bin_header']
+ wave_header = wave_data['wave_header']
+ self.string_indices_size = bin_header['sIndicesSize']
+ self.count = self.string_indices_size // 4
+ if self.count: # make sure we're in a text wave
+ assert TYPE_TABLE[wave_header['type']] is None, wave_header
+ self.setup()
+
+ def post_unpack(self, parents, data):
+ if not self.count:
+ return
+ wave_structure = parents[-1]
+ wave_data = self._get_structure_data(parents, data, wave_structure)
+ wave_header = wave_data['wave_header']
+ wdata = wave_data['wData']
+ strings = []
+ start = 0
+ for i,offset in enumerate(wave_data['sIndices']):
+ if offset > start:
+ chars = wdata[start:offset]
+ strings.append(b''.join(chars))
+ start = offset
+ elif offset == start:
+ strings.append(b'')
+ else:
+ raise ValueError((offset, wave_data['sIndices']))
+ wdata = _numpy.array(strings)
+ shape = [n for n in wave_header['nDim'] if n > 0] or (0,)
+ try:
+ wdata = wdata.reshape(shape)
+ except ValueError:
+ _LOG.error(
+ 'could not reshape strings from {} to {}'.format(
+ shape, wdata.shape))
+ raise
+ wave_data['wData'] = wdata
+
+
+class DynamicVersionField (_DynamicField):
+ def pre_pack(self, parents, byte_order):
+ raise NotImplementedError()
+
+ def post_unpack(self, parents, data):
+ wave_structure = parents[-1]
+ wave_data = self._get_structure_data(parents, data, wave_structure)
+ version = wave_data['version']
+ if wave_structure.byte_order in '@=':
+ need_to_reorder_bytes = _need_to_reorder_bytes(version)
+ wave_structure.byte_order = _byte_order(need_to_reorder_bytes)
+ _LOG.debug(
+ 'get byte order from version: {} (reorder? {})'.format(
+ wave_structure.byte_order, need_to_reorder_bytes))