From 06c94cd7806bbe1c170e0006a0b7c61f4a50a25c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 16 Jul 2012 13:15:34 -0400 Subject: [PATCH 01/76] Split IGOR handling out into its own repository. From bf9cd0a01312928dbe2f4769d1d1b7c032bd8ff3 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 4 Jun 2010 01:15:46 -0400 Subject: [PATCH 02/76] Add hooke.driver.igorbinarywave so Hooke can read Igor files. --- hooke/driver/igorbinarywave.py | 508 +++++++++++++++++++++++++++++++++ 1 file changed, 508 insertions(+) create mode 100644 hooke/driver/igorbinarywave.py diff --git a/hooke/driver/igorbinarywave.py b/hooke/driver/igorbinarywave.py new file mode 100644 index 0000000..45af198 --- /dev/null +++ b/hooke/driver/igorbinarywave.py @@ -0,0 +1,508 @@ +#!/usr/bin/python +# +# igorbinarywave provides pure Python interface between IGOR Binary +# Wave files and Numpy arrays. +# +# Copyright 2010 W. Trevor King + +# Based on WaveMetric's Technical Note 003, "Igor Binary Format" +# ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN003.zip +# From ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN000.txt +# We place no restrictions on copying Technical Notes, with the +# exception that you cannot resell them. So read, enjoy, and +# share. We hope IGOR Technical Notes will provide you with lots of +# valuable information while you are developing IGOR applications. + +import array +import struct +import sys +import types + +import numpy + + +__version__ = '0.1' + + +class Field (object): + """Represent a Structure field. + + See Also + -------- + Structure + """ + def __init__(self, format, name, default=None, help=None, count=1): + self.format = format # See the struct documentation + self.name = name + self.default = None + self.help = help + self.count = count + self.total_count = numpy.prod(count) + +class Structure (struct.Struct): + """Represent a C structure. + + A convenient wrapper around struct.Struct that uses Fields and + adds dict-handling methods for transparent name assignment. + + See Also + -------- + Field + + Examples + -------- + + Represent the C structure:: + + struct thing { + short version; + long size[3]; + } + + As + + >>> from pprint import pprint + >>> thing = Structure(name='thing', + ... fields=[Field('h', 'version'), Field('l', 'size', count=3)]) + >>> thing.set_byte_order('>') + >>> b = array.array('b', range(2+4*3)) + >>> d = thing.unpack_dict_from(buffer=b) + >>> pprint(d) + {'size': array([ 33752069, 101124105, 168496141]), 'version': 1} + >>> [hex(x) for x in d['size']] + ['0x2030405L', '0x6070809L', '0xa0b0c0dL'] + + You can even get fancy with multi-dimensional arrays. + + >>> thing = Structure(name='thing', + ... fields=[Field('h', 'version'), Field('l', 'size', count=(3,2))]) + >>> thing.set_byte_order('>') + >>> b = array.array('b', range(2+4*3*2)) + >>> d = thing.unpack_dict_from(buffer=b) + >>> d['size'].shape + (3, 2) + >>> pprint(d) + {'size': array([[ 33752069, 101124105], + [168496141, 235868177], + [303240213, 370612249]]), + 'version': 1} + """ + def __init__(self, name, fields, byte_order='='): + # '=' for native byte order, standard size and alignment + # See http://docs.python.org/library/struct for details + self.name = name + self.fields = fields + self.set_byte_order(byte_order) + + def __str__(self): + return self.name + + def set_byte_order(self, byte_order): + """Allow changing the format byte_order on the fly. + """ + if (hasattr(self, 'format') and self.format != None + and self.format.startswith(byte_order)): + return # no need to change anything + format = [] + for field in self.fields: + format.extend([field.format]*field.total_count) + struct.Struct.__init__(self, format=byte_order+''.join(format).replace('P', 'L')) + + def _flatten_args(self, args): + # handle Field.count > 0 + flat_args = [] + for a,f in zip(args, self.fields): + if f.total_count > 1: + flat_args.extend(a) + else: + flat_args.append(a) + return flat_args + + def _unflatten_args(self, args): + # handle Field.count > 0 + unflat_args = [] + i = 0 + for f in self.fields: + if f.total_count > 1: + data = numpy.array(args[i:i+f.total_count]) + data = data.reshape(f.count) + unflat_args.append(data) + else: + unflat_args.append(args[i]) + i += f.total_count + return unflat_args + + def pack(self, *args): + return struct.Struct.pack(self, *self._flatten_args(args)) + + def pack_into(self, buffer, offset, *args): + return struct.Struct.pack_into(self, buffer, offset, + *self._flatten_args(args)) + + def _clean_dict(self, dict): + for f in self.fields: + if f.name not in dict: + if f.default != None: + dict[f.name] = f.default + else: + raise ValueError('%s field not set for %s' + % f.name, self.__class__.__name__) + return dict + + def pack_dict(self, dict): + dict = self._clean_dict(dict) + return self.pack(*[dict[f.name] for f in self.fields]) + + def pack_dict_into(self, buffer, offset, dict={}): + dict = self._clean_dict(dict) + return self.pack_into(buffer, offset, + *[dict[f.name] for f in self.fields]) + + def unpack(self, string): + return self._unflatten_args(struct.Struct.unpack(self, string)) + + def unpack_from(self, buffer, offset=0): + return self._unflatten_args( + struct.Struct.unpack_from(self, buffer, offset)) + + def unpack_dict(self, string): + return dict(zip([f.name for f in self.fields], + self.unpack(string))) + + def unpack_dict_from(self, buffer, offset=0): + return dict(zip([f.name for f in self.fields], + self.unpack_from(buffer, offset))) + + +# Numpy doesn't support complex integers by default, see +# http://mail.python.org/pipermail/python-dev/2002-April/022408.html +# http://mail.scipy.org/pipermail/numpy-discussion/2007-October/029447.html +# So we roll our own types. See +# http://docs.scipy.org/doc/numpy/user/basics.rec.html +# http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html +complexInt8 = numpy.dtype([('real', numpy.int8), ('imag', numpy.int8)]) +complexInt16 = numpy.dtype([('real', numpy.int16), ('imag', numpy.int16)]) +complexInt32 = numpy.dtype([('real', numpy.int32), ('imag', numpy.int32)]) +complexUInt8 = numpy.dtype([('real', numpy.uint8), ('imag', numpy.uint8)]) +complexUInt16 = numpy.dtype([('real', numpy.uint16), ('imag', numpy.uint16)]) +complexUInt32 = numpy.dtype([('real', numpy.uint32), ('imag', numpy.uint32)]) + + +# Begin IGOR constants and typedefs from IgorBin.h + +# From IgorMath.h +TYPE_TABLE = { # (key: integer flag, value: numpy dtype) + 0:None, # Text wave, not handled in ReadWave.c + 1:numpy.complex, # NT_CMPLX, makes number complex. + 2:numpy.float32, # NT_FP32, 32 bit fp numbers. + 3:numpy.complex64, + 4:numpy.float64, # NT_FP64, 64 bit fp numbers. + 5:numpy.complex128, + 8:numpy.int8, # NT_I8, 8 bit signed integer. Requires Igor Pro + # 2.0 or later. + 9:complexInt8, + 0x10:numpy.int16,# NT_I16, 16 bit integer numbers. Requires Igor + # Pro 2.0 or later. + 0x11:complexInt16, + 0x20:numpy.int32,# NT_I32, 32 bit integer numbers. Requires Igor + # Pro 2.0 or later. + 0x21:complexInt32, +# 0x40:None, # NT_UNSIGNED, Makes above signed integers +# # unsigned. Requires Igor Pro 3.0 or later. + 0x48:numpy.uint8, + 0x49:complexUInt8, + 0x50:numpy.uint16, + 0x51:complexUInt16, + 0x60:numpy.uint32, + 0x61:complexUInt32, +} + +# From wave.h +MAXDIMS = 4 + +# From binary.h +BinHeaderCommon = Structure( # WTK: this one is mine. + name='BinHeaderCommon', + fields=[ + Field('h', 'version', help='Version number for backwards compatibility.'), + ]) + +BinHeader1 = Structure( + name='BinHeader1', + fields=[ + Field('h', 'version', help='Version number for backwards compatibility.'), + Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), + Field('h', 'checksum', help='Checksum over this header and the wave header.'), + ]) + +BinHeader2 = Structure( + name='BinHeader2', + fields=[ + Field('h', 'version', help='Version number for backwards compatibility.'), + Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), + Field('l', 'noteSize', help='The size of the note text.'), + Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'), + Field('h', 'checksum', help='Checksum over this header and the wave header.'), + ]) + +BinHeader3 = Structure( + name='BinHeader3', + fields=[ + Field('h', 'version', help='Version number for backwards compatibility.'), + Field('h', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), + Field('l', 'noteSize', help='The size of the note text.'), + Field('l', 'formulaSize', help='The size of the dependency formula, if any.'), + Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'), + Field('h', 'checksum', help='Checksum over this header and the wave header.'), + ]) + +BinHeader5 = Structure( + name='BinHeader5', + fields=[ + Field('h', 'version', help='Version number for backwards compatibility.'), + Field('h', 'checksum', help='Checksum over this header and the wave header.'), + Field('l', 'wfmSize', help='The size of the WaveHeader5 data structure plus the wave data.'), + Field('l', 'formulaSize', help='The size of the dependency formula, if any.'), + Field('l', 'noteSize', help='The size of the note text.'), + Field('l', 'dataEUnitsSize', help='The size of optional extended data units.'), + Field('l', 'dimEUnitsSize', help='The size of optional extended dimension units.', count=MAXDIMS), + Field('l', 'dimLabelsSize', help='The size of optional dimension labels.', count=MAXDIMS), + Field('l', 'sIndicesSize', help='The size of string indicies if this is a text wave.'), + Field('l', 'optionsSize1', default=0, help='Reserved. Write zero. Ignore on read.'), + Field('l', 'optionsSize2', default=0, help='Reserved. Write zero. Ignore on read.'), + ]) + + +# From wave.h +MAX_WAVE_NAME2 = 18 # Maximum length of wave name in version 1 and 2 + # files. Does not include the trailing null. +MAX_WAVE_NAME5 = 31 # Maximum length of wave name in version 5 + # files. Does not include the trailing null. +MAX_UNIT_CHARS = 3 + +# Header to an array of waveform data. + +WaveHeader2 = Structure( + name='WaveHeader2', + fields=[ + Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'), + Field('P', 'next', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME2+2), + Field('h', 'whVersion', default=0, help='Write 0. Ignore on read.'), + Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1), + Field('c', 'xUnits', default=0, help='Natural x-axis units go here - null if none.', count=MAX_UNIT_CHARS+1), + Field('l', 'npnts', help='Number of data points in wave.'), + Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('d', 'hsA', help='X value for point p = hsA*p + hsB'), + Field('d', 'hsB', help='X value for point p = hsA*p + hsB'), + Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('h', 'fsValid', help='True if full scale values have meaning.'), + Field('d', 'topFullScale', help='The min full scale value for wave.'), # sic, 'min' should probably be 'max' + Field('d', 'botFullScale', help='The min full scale value for wave.'), + Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'), + Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('L', 'creationDate', help='DateTime of creation. Not used in version 1 files.'), + Field('c', 'wUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=2), + Field('L', 'modDate', help='DateTime of last modification.'), + Field('P', 'waveNoteH', help='Used in memory only. Write zero. Ignore on read.'), + Field('f', 'wData', help='The start of the array of waveform data.', count=4), + ]) + +WaveHeader5 = Structure( + name='WaveHeader5', + fields=[ + Field('P', 'next', help='link to next wave in linked list.'), + Field('L', 'creationDate', help='DateTime of creation.'), + Field('L', 'modDate', help='DateTime of last modification.'), + Field('l', 'npnts', help='Total number of points (multiply dimensions up to first zero).'), + Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'), + Field('h', 'dLock', default=0, help='Reserved. Write zero. Ignore on read.'), + Field('c', 'whpad1', default=0, help='Reserved. Write zero. Ignore on read.', count=6), + Field('h', 'whVersion', default=1, help='Write 1. Ignore on read.'), + Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME5+1), + Field('l', 'whpad2', default=0, help='Reserved. Write zero. Ignore on read.'), + Field('P', 'dFolder', default=0, help='Used in memory only. Write zero. Ignore on read.'), + # Dimensioning info. [0] == rows, [1] == cols etc + Field('l', 'nDim', help='Number of of items in a dimension -- 0 means no data.', count=MAXDIMS), + Field('d', 'sfA', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS), + Field('d', 'sfB', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS), + # SI units + Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1), + Field('c', 'dimUnits', default=0, help='Natural dimension units go here - null if none.', count=(MAXDIMS, MAX_UNIT_CHARS+1)), + Field('h', 'fsValid', help='TRUE if full scale values have meaning.'), + Field('h', 'whpad3', default=0, help='Reserved. Write zero. Ignore on read.'), + Field('d', 'topFullScale', help='The max and max full scale value for wave'), # sic, probably "max and min" + Field('d', 'botFullScale', help='The max and max full scale value for wave.'), # sic, probably "max and min" + Field('P', 'dataEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('P', 'dimEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS), + Field('P', 'dimLabels', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS), + Field('P', 'waveNoteH', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('l', 'whUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=16), + # The following stuff is considered private to Igor. + Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'), + Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('h', 'whpad4', default=0, help='Reserved. Write zero. Ignore on read.'), + Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('P', 'sIndices', default=0, help='Used in memory only. Write zero. Ignore on read.'), + Field('f', 'wData', help='The start of the array of data. Must be 64 bit aligned.', count=1), + ]) + +# End IGOR constants and typedefs from IgorBin.h + +# Begin functions from ReadWave.c + +def need_to_reorder_bytes(version): + # If the low order byte of the version field of the BinHeader + # structure is zero then the file is from a platform that uses + # different byte-ordering and therefore all data will need to be + # reordered. + return version & 0xFF == 0 + +def byte_order(needToReorderBytes): + little_endian = sys.byteorder == 'little' + if needToReorderBytes: + little_endian = not little_endian + if little_endian: + return '<' # little-endian + return '>' # big-endian + +def version_structs(version, byte_order): + if version == 1: + bin = BinHeader1 + wave = WaveHeader2 + elif version == 2: + bin = BinHeader2 + wave = WaveHeader2 + elif version == 3: + bin = BinHeader3 + wave = WaveHeader2 + elif version == 5: + bin = BinHeader5 + wave = WaveHeader5 + else: + raise ValueError('This does not appear to be a valid Igor binary wave file. The version field = %d.\n', version); + checkSumSize = bin.size + wave.size + if version == 5: + checkSumSize -= 4 # Version 5 checksum does not include the wData field. + bin.set_byte_order(byte_order) + wave.set_byte_order(byte_order) + return (bin, wave, checkSumSize) + +def checksum(buffer, byte_order, oldcksum, numbytes): + x = numpy.ndarray( + (numbytes/2,), # 2 bytes to a short -- ignore trailing odd byte + dtype=numpy.dtype(byte_order+'h'), + buffer=buffer) + oldcksum += x.sum() + if oldcksum > 2**31: # fake the C implementation's int rollover + oldcksum %= 2**32 + if oldcksum > 2**31: + oldcksum -= 2**31 + return oldcksum & 0xffff + +# Translated from ReadWave() +def loadibw(filename): + if hasattr(filename, 'read'): + f = filename # filename is actually a stream object + else: + f = open(filename, 'rb') + try: + b = buffer(f.read(BinHeaderCommon.size)) + version = BinHeaderCommon.unpack_dict_from(b)['version'] + needToReorderBytes = need_to_reorder_bytes(version) + byteOrder = byte_order(needToReorderBytes) + + if needToReorderBytes: + BinHeaderCommon.set_byte_order(byteOrder) + version = BinHeaderCommon.unpack_dict_from(b)['version'] + bin_struct,wave_struct,checkSumSize = version_structs(version, byteOrder) + + b = buffer(b + f.read(bin_struct.size + wave_struct.size - BinHeaderCommon.size)) + c = checksum(b, byteOrder, 0, checkSumSize) + if c != 0: + raise ValueError('Error in checksum - should be 0, is %d. This does not appear to be a valid Igor binary wave file.' % c) + bin_info = bin_struct.unpack_dict_from(b) + wave_info = wave_struct.unpack_dict_from(b, offset=bin_struct.size) + if wave_info['type'] == 0: + raise NotImplementedError('Text wave') + if version in [1,2,3]: + tail = 16 # 16 = size of wData field in WaveHeader2 structure + waveDataSize = bin_info['wfmSize'] - wave_struct.size + # = bin_info['wfmSize']-16 - (wave_struct.size - tail) + else: + assert version == 5, version + tail = 4 # 4 = size of wData field in WaveHeader5 structure + waveDataSize = bin_info['wfmSize'] - (wave_struct.size - tail) + # dtype() wrapping to avoid numpy.generic and + # getset_descriptor issues with the builtin Numpy types + # (e.g. int32). It has no effect on our local complex + # integers. + t = numpy.dtype(TYPE_TABLE[wave_info['type']]) + assert waveDataSize == wave_info['npnts'] * t.itemsize, \ + ('%d, %d, %d, %s' % (waveDataSize, wave_info['npnts'], t.itemsize, t)) + tail_data = array.array('f', b[-tail:]) + data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail)) + data = numpy.ndarray( + wave_info['npnts'], + dtype=t.newbyteorder(byteOrder), + buffer=data_b + ) + finally: + if not hasattr(filename, 'read'): + f.close() + + return data, bin_info, wave_info + + +def saveibw(filename): + raise NotImplementedError + + +if __name__ == '__main__': + """IBW -> ASCII conversion + """ + import optparse + import sys + + p = optparse.OptionParser(version=__version__) + + p.add_option('-f', '--infile', dest='infile', metavar='FILE', + default='-', help='Input IGOR Binary Wave (.ibw) file.') + p.add_option('-o', '--outfile', dest='outfile', metavar='FILE', + default='-', help='File for ASCII output.') + p.add_option('-v', '--verbose', dest='verbose', default=0, + action='count', help='Increment verbosity') + p.add_option('-t', '--test', dest='test', default=False, + action='store_true', help='Run internal tests and exit.') + + options,args = p.parse_args() + + if options.test == True: + import doctest + num_failures,num_tests = doctest.testmod(verbose=options.verbose) + sys.exit(min(num_failures, 127)) + + if len(args) > 0 and options.infile == None: + options.infile = args[0] + if options.infile == '-': + options.infile = sys.stdin + if options.outfile == '-': + options.outfile = sys.stdout + + data,bin_info,wave_info = loadibw(options.infile) + numpy.savetxt(options.outfile, data, fmt='%g', delimiter='\n') + if options.verbose > 0: + import pprint + pprint.pprint(bin_info) + pprint.pprint(wave_info) From f34cd7f04cd21e595686a1c466f0b0b9cedd3ab1 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 4 Jun 2010 01:16:51 -0400 Subject: [PATCH 03/76] Ran update_copyright.py on igorbinarywave.py --- hooke/driver/igorbinarywave.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/hooke/driver/igorbinarywave.py b/hooke/driver/igorbinarywave.py index 45af198..d0cf2e9 100644 --- a/hooke/driver/igorbinarywave.py +++ b/hooke/driver/igorbinarywave.py @@ -3,7 +3,23 @@ # igorbinarywave provides pure Python interface between IGOR Binary # Wave files and Numpy arrays. # -# Copyright 2010 W. Trevor King +# Copyright (C) 2010 W. Trevor King +# +# This file is part of Hooke. +# +# Hooke is free software: you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation, either +# version 3 of the License, or (at your option) any later version. +# +# Hooke is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with Hooke. If not, see +# . # Based on WaveMetric's Technical Note 003, "Igor Binary Format" # ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN003.zip From 101d0300c24d79689dd79a42af418c6138805868 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 4 Jun 2010 02:46:11 -0400 Subject: [PATCH 04/76] Automatically split version 5 Igor wave files into columns --- hooke/driver/igorbinarywave.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/hooke/driver/igorbinarywave.py b/hooke/driver/igorbinarywave.py index d0cf2e9..07e3f03 100644 --- a/hooke/driver/igorbinarywave.py +++ b/hooke/driver/igorbinarywave.py @@ -1,8 +1,5 @@ #!/usr/bin/python # -# igorbinarywave provides pure Python interface between IGOR Binary -# Wave files and Numpy arrays. -# # Copyright (C) 2010 W. Trevor King # # This file is part of Hooke. @@ -21,6 +18,14 @@ # License along with Hooke. If not, see # . +"""igorbinarywave provides pure Python interface between IGOR Binary +Wave files and Numpy arrays. + +This is basically a stand-alone package that we bundle into Hooke for +convenience. It is used by the mfp*d drivers, whose data is saved in +IBW files. +""" + # Based on WaveMetric's Technical Note 003, "Igor Binary Format" # ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN003.zip # From ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN000.txt @@ -469,10 +474,15 @@ def loadibw(filename): ('%d, %d, %d, %s' % (waveDataSize, wave_info['npnts'], t.itemsize, t)) tail_data = array.array('f', b[-tail:]) data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail)) + if version == 5: + shape = [n for n in wave_info['nDim'] if n > 0] + else: + shape = (wave_info['npnts'],) data = numpy.ndarray( - wave_info['npnts'], + shape=shape, dtype=t.newbyteorder(byteOrder), - buffer=data_b + buffer=data_b, + order='F', ) finally: if not hasattr(filename, 'read'): @@ -517,7 +527,7 @@ def saveibw(filename): options.outfile = sys.stdout data,bin_info,wave_info = loadibw(options.infile) - numpy.savetxt(options.outfile, data, fmt='%g', delimiter='\n') + numpy.savetxt(options.outfile, data, fmt='%g', delimiter='\t') if options.verbose > 0: import pprint pprint.pprint(bin_info) From ce315c87a6b49b53a5c68bf3e9e8164bce69a0c3 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 4 Jun 2010 03:28:19 -0400 Subject: [PATCH 05/76] Added post-data optional field processing to igorbinarywave.loadibw. --- hooke/driver/igorbinarywave.py | 69 ++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/hooke/driver/igorbinarywave.py b/hooke/driver/igorbinarywave.py index 07e3f03..a25f33e 100644 --- a/hooke/driver/igorbinarywave.py +++ b/hooke/driver/igorbinarywave.py @@ -484,6 +484,75 @@ def loadibw(filename): buffer=data_b, order='F', ) + + if version == 1: + pass # No post-data information + elif version == 2: + # Post-data info: + # * 16 bytes of padding + # * Optional wave note data + pad_b = buffer(f.read(16)) # skip the padding + assert max(pad_b) == 0, pad_b + bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() + elif version == 3: + # Post-data info: + # * 16 bytes of padding + # * Optional wave note data + # * 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. + """ + pad_b = buffer(f.read(16)) # skip the padding + assert max(pad_b) == 0, pad_b + bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() + bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip() + elif version == 5: + # Post-data info: + # * Optional wave dependency formula + # * Optional wave note data + # * Optional extended data units data + # * Optional extended dimension units data + # * Optional dimension label data + # * String indices used for text waves only + """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. + + 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. + """ + bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip() + bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() + bin_info['dataEUnits'] = str(f.read(bin_info['dataEUnitsSize'])).strip() + bin_info['dimEUnits'] = [ + str(f.read(size)).strip() for size in bin_info['dimEUnitsSize']] + bin_info['dimLabels'] = [] + for size in bin_info['dimLabelsSize']: + labels = str(f.read(size)).split(chr(0)) # split null-delimited strings + bin_info['dimLabels'].append([L for L in labels if len(L) > 0]) + if wave_info['type'] == 0: # text wave + bin_info['sIndices'] = f.read(bin_info['sIndicesSize']) + finally: if not hasattr(filename, 'read'): f.close() From 32567bfc6e094032b0d4aacedf97a47e5e7c9751 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 2 Aug 2010 20:10:15 -0400 Subject: [PATCH 06/76] Ran update_copyright.py --- hooke/driver/igorbinarywave.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/hooke/driver/igorbinarywave.py b/hooke/driver/igorbinarywave.py index a25f33e..209bbc7 100644 --- a/hooke/driver/igorbinarywave.py +++ b/hooke/driver/igorbinarywave.py @@ -4,15 +4,15 @@ # # This file is part of Hooke. # -# Hooke is free software: you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation, either -# version 3 of the License, or (at your option) any later version. +# Hooke is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. # -# Hooke is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. +# Hooke is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +# Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with Hooke. If not, see From 18db1756e78a212480c82e37858244f09eb28cf9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 8 Sep 2010 10:30:28 -0400 Subject: [PATCH 07/76] Move hooke.driver.igorbinarywave to hooke.util.igorbinarywave. It's a utility for the MFP3D driver, not a driver in its own right. --- hooke/{driver => util}/igorbinarywave.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename hooke/{driver => util}/igorbinarywave.py (100%) diff --git a/hooke/driver/igorbinarywave.py b/hooke/util/igorbinarywave.py similarity index 100% rename from hooke/driver/igorbinarywave.py rename to hooke/util/igorbinarywave.py From ac028327b284f8fe28570f867a7aaef7972dde97 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 28 Oct 2010 12:39:29 -0400 Subject: [PATCH 08/76] Add -n/--not-strict to igorbinarywave.py (currently for IBW files with non-empty padding). --- hooke/util/igorbinarywave.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/hooke/util/igorbinarywave.py b/hooke/util/igorbinarywave.py index 209bbc7..a361a76 100644 --- a/hooke/util/igorbinarywave.py +++ b/hooke/util/igorbinarywave.py @@ -433,7 +433,7 @@ def checksum(buffer, byte_order, oldcksum, numbytes): return oldcksum & 0xffff # Translated from ReadWave() -def loadibw(filename): +def loadibw(filename, strict=True): if hasattr(filename, 'read'): f = filename # filename is actually a stream object else: @@ -492,7 +492,11 @@ def loadibw(filename): # * 16 bytes of padding # * Optional wave note data pad_b = buffer(f.read(16)) # skip the padding - assert max(pad_b) == 0, pad_b + if max(pad_b) != 0: + if strict: + assert max(pad_b) == 0, pad_b + else: + print sys.stderr, 'warning: post-data padding not zero: %s.' % pad_b bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() elif version == 3: # Post-data info: @@ -507,7 +511,11 @@ def loadibw(filename): no trailing null byte. """ pad_b = buffer(f.read(16)) # skip the padding - assert max(pad_b) == 0, pad_b + if max(pad_b) != 0: + if strict: + assert max(pad_b) == 0, pad_b + else: + print sys.stderr, 'warning: post-data padding not zero: %s.' % pad_b bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip() elif version == 5: @@ -578,6 +586,8 @@ def saveibw(filename): default='-', help='File for ASCII output.') p.add_option('-v', '--verbose', dest='verbose', default=0, action='count', help='Increment verbosity') + p.add_option('-n', '--not-strict', dest='strict', default=True, + action='store_false', help='Attempt to parse invalid IBW files.') p.add_option('-t', '--test', dest='test', default=False, action='store_true', help='Run internal tests and exit.') @@ -595,7 +605,7 @@ def saveibw(filename): if options.outfile == '-': options.outfile = sys.stdout - data,bin_info,wave_info = loadibw(options.infile) + data,bin_info,wave_info = loadibw(options.infile, strict=options.strict) numpy.savetxt(options.outfile, data, fmt='%g', delimiter='\t') if options.verbose > 0: import pprint From 155a2be2d9650dcb8e3407a0f8242b9687c94018 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 16 Jul 2012 14:56:43 -0400 Subject: [PATCH 09/76] Split igorbinarywave.py off of Hooke into its own package. --- .gitignore | 5 ++ .mailmap | 1 + .update-copyright.conf | 18 ++++ README | 87 +++++++++++++++++++ bin/igorbinarywave.py | 39 +++++++++ igor/__init__.py | 5 ++ .../igorbinarywave.py => igor/binarywave.py | 54 +----------- setup.py | 40 +++++++++ 8 files changed, 196 insertions(+), 53 deletions(-) create mode 100644 .gitignore create mode 100644 .mailmap create mode 100644 .update-copyright.conf create mode 100644 README create mode 100755 bin/igorbinarywave.py create mode 100644 igor/__init__.py rename hooke/util/igorbinarywave.py => igor/binarywave.py (93%) create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7cf134c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +MANIFEST +build/ +dist/ +igor.egg-info/ +*.pyc diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..4a905eb --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +W. Trevor King diff --git a/.update-copyright.conf b/.update-copyright.conf new file mode 100644 index 0000000..9ce16c9 --- /dev/null +++ b/.update-copyright.conf @@ -0,0 +1,18 @@ +[project] +name: igor +vcs: Git + +[files] +authors: yes +files: yes +ignored: COPYING, README, .update-copyright.conf, .git*, test/* + +[copyright] +short: %(project)s comes with ABSOLUTELY NO WARRANTY and is licensed under the GNU Lesser General Public License. For details, %%(get-details)s. +long: This file is part of %(project)s. + + %(project)s is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + %(project)s is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with %(project)s. If not, see . diff --git a/README b/README new file mode 100644 index 0000000..f76d225 --- /dev/null +++ b/README @@ -0,0 +1,87 @@ +igor: Interface for reading binary IGOR files. + +Read Igor Binary Waves (.ibw) and ... written by WaveMetrics' IGOR +package. + +Installation +============ + +Packages +-------- + +Gentoo +~~~~~~ + +I've packaged `igor` for Gentoo. You need layman_ and my `wtk +overlay`_. Install with:: + + # emerge -av app-portage/layman + # layman --add wtk + # emerge -av sci-misc/igor + +Dependencies +------------ + +If you're installing by hand or packaging calibcant for another +distribution, you'll need the following dependencies: + +=========== ================= ============================ +Package Debian_ Gentoo_ +=========== ================= ============================ +Numpy_ python-numpy dev-python/numpy +Matplotlib_ python-matplotlib dev-python/matplotlib +Nose_ python-nose dev-python/nose +=========== ================= ============================ + +Installing by hand +------------------ + +`igor` is available as a Git_ repository:: + + $ git clone git://tremily.us/igor.git + +See the homepage_ for details. To install the checkout, run the +standard:: + + $ python setup.py install + + +Usage +===== + +See the module docstrings for simple examples. + + +Testing +======= + +Run internal unit tests with:: + + $ nosetests --with-doctest --doctest-tests igor + + +Licence +======= + +This project is distributed under the `GNU General Public License +Version 3`_ or greater. + + +Author +====== + +W. Trevor King +wking@tremily.us +Copyright 2008-2012 + + +.. _layman: http://layman.sourceforge.net/ +.. _wtk overlay: http://blog.tremily.us/posts/Gentoo_overlay/ +.. _Debian: http://www.debian.org/ +.. _Gentoo: http://www.gentoo.org/ +.. _NumPy: http://numpy.scipy.org/ +.. _Matplotlib: http://matplotlib.sourceforge.net/ +.. _Nose: http://somethingaboutorange.com/mrl/projects/nose/ +.. _Git: http://git-scm.com/ +.. _homepage: http://blog.tremily.us/posts/calibcant/ +.. _GNU General Public License Version 3: http://www.gnu.org/licenses/gpl.txt diff --git a/bin/igorbinarywave.py b/bin/igorbinarywave.py new file mode 100755 index 0000000..435fdca --- /dev/null +++ b/bin/igorbinarywave.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +"IBW -> ASCII conversion" + +import optparse +import pprint +import sys + +import numpy + +from igor import __version__ +from igor.binarywave import loadibw + + +p = optparse.OptionParser(version=__version__) + +p.add_option('-f', '--infile', dest='infile', metavar='FILE', + default='-', help='Input IGOR Binary Wave (.ibw) file.') +p.add_option('-o', '--outfile', dest='outfile', metavar='FILE', + default='-', help='File for ASCII output.') +p.add_option('-v', '--verbose', dest='verbose', default=0, + action='count', help='Increment verbosity') +p.add_option('-n', '--not-strict', dest='strict', default=True, + action='store_false', help='Attempt to parse invalid IBW files.') + +options,args = p.parse_args() + +if len(args) > 0 and options.infile == None: + options.infile = args[0] +if options.infile == '-': + options.infile = sys.stdin +if options.outfile == '-': + options.outfile = sys.stdout + +data,bin_info,wave_info = loadibw(options.infile, strict=options.strict) +numpy.savetxt(options.outfile, data, fmt='%g', delimiter='\t') +if options.verbose > 0: + pprint.pprint(bin_info) + pprint.pprint(wave_info) diff --git a/igor/__init__.py b/igor/__init__.py new file mode 100644 index 0000000..f7ea88f --- /dev/null +++ b/igor/__init__.py @@ -0,0 +1,5 @@ +# Copyright + +"Interface for reading binary IGOR files." + +__version__ = '0.2' diff --git a/hooke/util/igorbinarywave.py b/igor/binarywave.py similarity index 93% rename from hooke/util/igorbinarywave.py rename to igor/binarywave.py index a361a76..b2fbe6c 100644 --- a/hooke/util/igorbinarywave.py +++ b/igor/binarywave.py @@ -1,5 +1,3 @@ -#!/usr/bin/python -# # Copyright (C) 2010 W. Trevor King # # This file is part of Hooke. @@ -18,13 +16,7 @@ # License along with Hooke. If not, see # . -"""igorbinarywave provides pure Python interface between IGOR Binary -Wave files and Numpy arrays. - -This is basically a stand-alone package that we bundle into Hooke for -convenience. It is used by the mfp*d drivers, whose data is saved in -IBW files. -""" +"Read IGOR Binary Wave files into Numpy arrays." # Based on WaveMetric's Technical Note 003, "Igor Binary Format" # ftp://ftp.wavemetrics.net/IgorPro/Technical_Notes/TN003.zip @@ -42,9 +34,6 @@ import numpy -__version__ = '0.1' - - class Field (object): """Represent a Structure field. @@ -570,44 +559,3 @@ def loadibw(filename, strict=True): def saveibw(filename): raise NotImplementedError - - -if __name__ == '__main__': - """IBW -> ASCII conversion - """ - import optparse - import sys - - p = optparse.OptionParser(version=__version__) - - p.add_option('-f', '--infile', dest='infile', metavar='FILE', - default='-', help='Input IGOR Binary Wave (.ibw) file.') - p.add_option('-o', '--outfile', dest='outfile', metavar='FILE', - default='-', help='File for ASCII output.') - p.add_option('-v', '--verbose', dest='verbose', default=0, - action='count', help='Increment verbosity') - p.add_option('-n', '--not-strict', dest='strict', default=True, - action='store_false', help='Attempt to parse invalid IBW files.') - p.add_option('-t', '--test', dest='test', default=False, - action='store_true', help='Run internal tests and exit.') - - options,args = p.parse_args() - - if options.test == True: - import doctest - num_failures,num_tests = doctest.testmod(verbose=options.verbose) - sys.exit(min(num_failures, 127)) - - if len(args) > 0 and options.infile == None: - options.infile = args[0] - if options.infile == '-': - options.infile = sys.stdin - if options.outfile == '-': - options.outfile = sys.stdout - - data,bin_info,wave_info = loadibw(options.infile, strict=options.strict) - numpy.savetxt(options.outfile, data, fmt='%g', delimiter='\t') - if options.verbose > 0: - import pprint - pprint.pprint(bin_info) - pprint.pprint(wave_info) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c63b48b --- /dev/null +++ b/setup.py @@ -0,0 +1,40 @@ +# Copyright + +"igor: interface for reading binary IGOR files." + +from distutils.core import setup +import os.path + +from igor import __version__ + + +package_name = 'igor' +_this_dir = os.path.dirname(__file__) + +setup(name=package_name, + version=__version__, + maintainer='W. Trevor King', + maintainer_email='wking@tremily.us', + url='http://blog.tremily.us/posts/%s/'.format(package_name), + download_url='http://git.tremily.us/?p={}.git;a=snapshot;h=v{};sf=tgz'.format(package_name, __version__), + license='GNU General Public License (GPL)', + platforms=['all'], + description=__doc__, + long_description=open(os.path.join(_this_dir, 'README'), 'r').read(), + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Intended Audience :: Developers', + 'Operating System :: OS Independent', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Programming Language :: Python', + 'Topic :: Scientific/Engineering', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + packages=[ + 'igor', + ], + scripts=[ + 'bin/igorbinarywave.py', + ], + provides=['igor (%s)' % __version__], + ) From 9b397d1478c81db165d76031622d2002d7dcf6e6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 16 Jul 2012 15:17:03 -0400 Subject: [PATCH 10/76] Upgrade to Python 2.7+ string formatting. --- igor/binarywave.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index b2fbe6c..09d06dc 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -155,8 +155,8 @@ def _clean_dict(self, dict): if f.default != None: dict[f.name] = f.default else: - raise ValueError('%s field not set for %s' - % f.name, self.__class__.__name__) + raise ValueError('{} field not set for {}'.format( + f.name, self.__class__.__name__)) return dict def pack_dict(self, dict): @@ -401,7 +401,9 @@ def version_structs(version, byte_order): bin = BinHeader5 wave = WaveHeader5 else: - raise ValueError('This does not appear to be a valid Igor binary wave file. The version field = %d.\n', version); + raise ValueError( + ('This does not appear to be a valid Igor binary wave file. ' + 'The version field = {}.\n').format(version)) checkSumSize = bin.size + wave.size if version == 5: checkSumSize -= 4 # Version 5 checksum does not include the wData field. @@ -441,7 +443,9 @@ def loadibw(filename, strict=True): b = buffer(b + f.read(bin_struct.size + wave_struct.size - BinHeaderCommon.size)) c = checksum(b, byteOrder, 0, checkSumSize) if c != 0: - raise ValueError('Error in checksum - should be 0, is %d. This does not appear to be a valid Igor binary wave file.' % c) + raise ValueError( + ('This does not appear to be a valid Igor binary wave file. ' + 'Error in checksum: should be 0, is {}.').format(c)) bin_info = bin_struct.unpack_dict_from(b) wave_info = wave_struct.unpack_dict_from(b, offset=bin_struct.size) if wave_info['type'] == 0: @@ -459,8 +463,9 @@ def loadibw(filename, strict=True): # (e.g. int32). It has no effect on our local complex # integers. t = numpy.dtype(TYPE_TABLE[wave_info['type']]) - assert waveDataSize == wave_info['npnts'] * t.itemsize, \ - ('%d, %d, %d, %s' % (waveDataSize, wave_info['npnts'], t.itemsize, t)) + assert waveDataSize == wave_info['npnts'] * t.itemsize, ( + '{}, {}, {}, {}'.format( + waveDataSize, wave_info['npnts'], t.itemsize, t)) tail_data = array.array('f', b[-tail:]) data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail)) if version == 5: @@ -485,7 +490,9 @@ def loadibw(filename, strict=True): if strict: assert max(pad_b) == 0, pad_b else: - print sys.stderr, 'warning: post-data padding not zero: %s.' % pad_b + sys.stderr.write( + 'warning: post-data padding not zero: {}\n'.format( + pad_b)) bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() elif version == 3: # Post-data info: @@ -504,7 +511,9 @@ def loadibw(filename, strict=True): if strict: assert max(pad_b) == 0, pad_b else: - print sys.stderr, 'warning: post-data padding not zero: %s.' % pad_b + sys.stderr.write( + 'warning: post-data padding not zero: {}\n'.format( + pad_b)) bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip() elif version == 5: From 433ce470115af565a0ed7b771fe09c3c4687a439 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 16 Jul 2012 15:51:14 -0400 Subject: [PATCH 11/76] Pull null-buffer check out into its own function: binarywave.assert_null. --- igor/binarywave.py | 62 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index 09d06dc..3429ad1 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -423,6 +423,52 @@ def checksum(buffer, byte_order, oldcksum, numbytes): oldcksum -= 2**31 return oldcksum & 0xffff +def hex_bytes(buffer, spaces=None): + r"""Pretty-printing for binary buffers. + + >>> hex_bytes(buffer('\x00\x01\x02\x03\x04')) + '0001020304' + >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=1) + '00 01 02 03 04' + >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=2) + '0001 0203 04' + >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=2) + '0001 0203 0405 06' + >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=3) + '000102 030405 06' + """ + hex_bytes = ['{:02x}'.format(ord(x)) for x in buffer] + if spaces is None: + return ''.join(hex_bytes) + elif spaces is 1: + return ' '.join(hex_bytes) + for i in range(len(hex_bytes)//spaces): + hex_bytes.insert((spaces+1)*(i+1)-1, ' ') + return ''.join(hex_bytes) + +def assert_null(buffer, strict=True): + r"""Ensure an input buffer is entirely zero. + + >>> assert_null(buffer('')) + >>> assert_null(buffer('\x00\x00')) + >>> assert_null(buffer('\x00\x01\x02\x03')) + Traceback (most recent call last): + ... + ValueError: 00 01 02 03 + >>> stderr = sys.stderr + >>> sys.stderr = sys.stdout + >>> assert_null(buffer('\x00\x01\x02\x03'), strict=False) + warning: post-data padding not zero: 00 01 02 03 + >>> sys.stderr = stderr + """ + if buffer and ord(max(buffer)) != 0: + hex_string = hex_bytes(buffer, spaces=1) + if strict: + raise ValueError(hex_string) + else: + sys.stderr.write( + 'warning: post-data padding not zero: {}\n'.format(hex_string)) + # Translated from ReadWave() def loadibw(filename, strict=True): if hasattr(filename, 'read'): @@ -486,13 +532,7 @@ def loadibw(filename, strict=True): # * 16 bytes of padding # * Optional wave note data pad_b = buffer(f.read(16)) # skip the padding - if max(pad_b) != 0: - if strict: - assert max(pad_b) == 0, pad_b - else: - sys.stderr.write( - 'warning: post-data padding not zero: {}\n'.format( - pad_b)) + assert_null(pad_b, strict=strict) bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() elif version == 3: # Post-data info: @@ -507,13 +547,7 @@ def loadibw(filename, strict=True): no trailing null byte. """ pad_b = buffer(f.read(16)) # skip the padding - if max(pad_b) != 0: - if strict: - assert max(pad_b) == 0, pad_b - else: - sys.stderr.write( - 'warning: post-data padding not zero: {}\n'.format( - pad_b)) + assert_null(pad_b, strict=strict) bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip() elif version == 5: From 73183fbf7a4697252c6b9664eeeba4cff4497e84 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 16 Jul 2012 16:28:43 -0400 Subject: [PATCH 12/76] Restore native byte order before running need_to_reorder_bytes. Otherwise an earier switch to a non-native byte ordering will confuse the current load. --- igor/binarywave.py | 1 + 1 file changed, 1 insertion(+) diff --git a/igor/binarywave.py b/igor/binarywave.py index 3429ad1..ae60f14 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -476,6 +476,7 @@ def loadibw(filename, strict=True): else: f = open(filename, 'rb') try: + BinHeaderCommon.set_byte_order('=') b = buffer(f.read(BinHeaderCommon.size)) version = BinHeaderCommon.unpack_dict_from(b)['version'] needToReorderBytes = need_to_reorder_bytes(version) From 8df86762588d9e4dc61cafe40170dd01bd3da950 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 16 Jul 2012 22:01:57 -0400 Subject: [PATCH 13/76] Add support for zero-length waves. --- igor/binarywave.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index ae60f14..ebb5fcb 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -34,6 +34,9 @@ import numpy +_buffer = buffer # save builtin buffer for clobbered situations + + class Field (object): """Represent a Structure field. @@ -172,8 +175,24 @@ def unpack(self, string): return self._unflatten_args(struct.Struct.unpack(self, string)) def unpack_from(self, buffer, offset=0): - return self._unflatten_args( - struct.Struct.unpack_from(self, buffer, offset)) + try: + args = struct.Struct.unpack_from(self, buffer, offset) + except struct.error as e: + if not self.name in ('WaveHeader2', 'WaveHeader5'): + raise + # HACK! For WaveHeader5, when npnts is 0, wData is + # optional. If we couldn't unpack the structure, fill in + # wData with zeros and try again, asserting that npnts is + # zero. + if len(buffer) - offset < self.size: + # missing wData? Pad with zeros + buffer += _buffer('\x00'*(self.size + offset - len(buffer))) + args = struct.Struct.unpack_from(self, buffer, offset) + unpacked = self._unflatten_args(args) + data = dict(zip([f.name for f in self.fields], + unpacked)) + assert data['npnts'] == 0, data['npnts'] + return self._unflatten_args(args) def unpack_dict(self, string): return dict(zip([f.name for f in self.fields], @@ -513,12 +532,15 @@ def loadibw(filename, strict=True): assert waveDataSize == wave_info['npnts'] * t.itemsize, ( '{}, {}, {}, {}'.format( waveDataSize, wave_info['npnts'], t.itemsize, t)) - tail_data = array.array('f', b[-tail:]) - data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail)) if version == 5: - shape = [n for n in wave_info['nDim'] if n > 0] + shape = [n for n in wave_info['nDim'] if n > 0] or (0,) else: shape = (wave_info['npnts'],) + if wave_info['npnts'] == 0: + data_b = buffer('') + else: + tail_data = array.array('f', b[-tail:]) + data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail)) data = numpy.ndarray( shape=shape, dtype=t.newbyteorder(byteOrder), From 41e654728a35586ca05fc5d72edd4095da2860bc Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 16 Jul 2012 22:40:10 -0400 Subject: [PATCH 14/76] Add support for text waves. I'm confident in the bytes -> strings conversion, but not entirely sure how the strings get mapped into multidimensional arrays. --- igor/binarywave.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index ebb5fcb..7953ff1 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -514,8 +514,6 @@ def loadibw(filename, strict=True): 'Error in checksum: should be 0, is {}.').format(c)) bin_info = bin_struct.unpack_dict_from(b) wave_info = wave_struct.unpack_dict_from(b, offset=bin_struct.size) - if wave_info['type'] == 0: - raise NotImplementedError('Text wave') if version in [1,2,3]: tail = 16 # 16 = size of wData field in WaveHeader2 structure waveDataSize = bin_info['wfmSize'] - wave_struct.size @@ -528,14 +526,18 @@ def loadibw(filename, strict=True): # getset_descriptor issues with the builtin Numpy types # (e.g. int32). It has no effect on our local complex # integers. - t = numpy.dtype(TYPE_TABLE[wave_info['type']]) - assert waveDataSize == wave_info['npnts'] * t.itemsize, ( - '{}, {}, {}, {}'.format( - waveDataSize, wave_info['npnts'], t.itemsize, t)) if version == 5: shape = [n for n in wave_info['nDim'] if n > 0] or (0,) else: shape = (wave_info['npnts'],) + if wave_info['type'] == 0: # text wave + shape = (waveDataSize,) + t = numpy.dtype(numpy.int8) + else: + t = numpy.dtype(TYPE_TABLE[wave_info['type']]) + assert waveDataSize == wave_info['npnts'] * t.itemsize, ( + '{}, {}, {}, {}'.format( + waveDataSize, wave_info['npnts'], t.itemsize, t)) if wave_info['npnts'] == 0: data_b = buffer('') else: @@ -616,6 +618,21 @@ def loadibw(filename, strict=True): if wave_info['type'] == 0: # text wave bin_info['sIndices'] = f.read(bin_info['sIndicesSize']) + if wave_info['type'] == 0: # text wave + # use sIndices to split data into strings + strings = [] + start = 0 + for i,string_index in enumerate(bin_info['sIndices']): + offset = ord(string_index) + if offset > start: + string = data[start:offset] + strings.append(''.join(chr(x) for x in string)) + start = offset + else: + assert offset == 0, offset + data = numpy.array(strings) + shape = [n for n in wave_info['nDim'] if n > 0] or (0,) + data.reshape(shape) finally: if not hasattr(filename, 'read'): f.close() From 2c97e5ad3f287a4e4b1600968f83886de34cfaf1 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 16 Jul 2012 23:01:18 -0400 Subject: [PATCH 15/76] Preliminary support for dependent formula waves. --- igor/binarywave.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index 7953ff1..856c85f 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -530,14 +530,16 @@ def loadibw(filename, strict=True): shape = [n for n in wave_info['nDim'] if n > 0] or (0,) else: shape = (wave_info['npnts'],) + t = numpy.dtype(numpy.int8) # setup a safe default if wave_info['type'] == 0: # text wave shape = (waveDataSize,) - t = numpy.dtype(numpy.int8) - else: + elif wave_info['type'] in TYPE_TABLE or wave_info['npnts']: t = numpy.dtype(TYPE_TABLE[wave_info['type']]) assert waveDataSize == wave_info['npnts'] * t.itemsize, ( '{}, {}, {}, {}'.format( waveDataSize, wave_info['npnts'], t.itemsize, t)) + else: + pass # formula waves if wave_info['npnts'] == 0: data_b = buffer('') else: From c9344ceb607d7b9bb3fde6f5a64943507454b9e7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 16 Jul 2012 23:02:11 -0400 Subject: [PATCH 16/76] Add the test suite that's been driving today's changes. Samples are from TN003.zip, downloaded from ftp://www.wavemetrics.net/IgorPro/Technical_Notes/TN003.zip --- README | 2 +- test/data/README | 1 + test/data/mac-double.ibw | Bin 0 -> 182 bytes test/data/mac-textWave.ibw | Bin 0 -> 422 bytes test/data/mac-version2.ibw | Bin 0 -> 177 bytes test/data/mac-version3Dependent.ibw | Bin 0 -> 150 bytes test/data/mac-version5.ibw | Bin 0 -> 483 bytes test/data/mac-zeroPointWave.ibw | Bin 0 -> 384 bytes test/data/win-double.ibw | Bin 0 -> 182 bytes test/data/win-textWave.ibw | Bin 0 -> 422 bytes test/data/win-version2.ibw | Bin 0 -> 177 bytes test/data/win-version5.ibw | Bin 0 -> 483 bytes test/data/win-zeroPointWave.ibw | Bin 0 -> 384 bytes test/test.py | 614 ++++++++++++++++++++++++++++ 14 files changed, 616 insertions(+), 1 deletion(-) create mode 100644 test/data/README create mode 100644 test/data/mac-double.ibw create mode 100644 test/data/mac-textWave.ibw create mode 100644 test/data/mac-version2.ibw create mode 100644 test/data/mac-version3Dependent.ibw create mode 100644 test/data/mac-version5.ibw create mode 100644 test/data/mac-zeroPointWave.ibw create mode 100644 test/data/win-double.ibw create mode 100644 test/data/win-textWave.ibw create mode 100644 test/data/win-version2.ibw create mode 100644 test/data/win-version5.ibw create mode 100644 test/data/win-zeroPointWave.ibw create mode 100644 test/test.py diff --git a/README b/README index f76d225..6b73aae 100644 --- a/README +++ b/README @@ -57,7 +57,7 @@ Testing Run internal unit tests with:: - $ nosetests --with-doctest --doctest-tests igor + $ nosetests --with-doctest --doctest-tests igor test Licence diff --git a/test/data/README b/test/data/README new file mode 100644 index 0000000..4f621bf --- /dev/null +++ b/test/data/README @@ -0,0 +1 @@ +.ibw samples are from TN003.zip. diff --git a/test/data/mac-double.ibw b/test/data/mac-double.ibw new file mode 100644 index 0000000000000000000000000000000000000000..9518508867b743fdbed9757e390341eb3c55ec6b GIT binary patch literal 182 zcmZQzVqjoc2E<^HWXQk*Vx;7kCgr3;#PE=;3=H-k@W^A4+Vo;Z69WT;WMFU*fhchh RfYKaL8mbeU>B>$F3;>`84Hp0a literal 0 HcmV?d00001 diff --git a/test/data/mac-textWave.ibw b/test/data/mac-textWave.ibw new file mode 100644 index 0000000000000000000000000000000000000000..4d8334e076afed1fb87e92691687171af5dbddc5 GIT binary patch literal 422 zcmZQz72U+Zz!=0p8W2HNzUf7HJrMT;#aNNV8B0a0OPZ$hVX29Tp6gS1^}1k5BC57 literal 0 HcmV?d00001 diff --git a/test/data/win-double.ibw b/test/data/win-double.ibw new file mode 100644 index 0000000000000000000000000000000000000000..ec768b878c681ecb17ee8efc0bbcc5143c3e1620 GIT binary patch literal 182 zcmZQ#SjK<=N`)+7PD*}hQcfyD92<)jN$`U`5(h7{X~v693=9wwrclHI!V-Ye98j78 IN@JQ10E!I^vH$=8 literal 0 HcmV?d00001 diff --git a/test/data/win-textWave.ibw b/test/data/win-textWave.ibw new file mode 100644 index 0000000000000000000000000000000000000000..416cc0210555bdda7f461cab59fbc687b46aa33d GIT binary patch literal 422 zcmZQ&*ufCQ$UqVhK~r5H{$f)<5VN9)GcuHVv8IU=Ly8qG9TW z0*we@1+a4k|9IGzGrWHz literal 0 HcmV?d00001 diff --git a/test/data/win-version2.ibw b/test/data/win-version2.ibw new file mode 100644 index 0000000000000000000000000000000000000000..1a3f7a0143f940985011265b3b6f463de434e555 GIT binary patch literal 177 zcmZQ#n8d)qzz@VAAkM-Brpr=`iZk=`jKCZm1S^vG2YVzAUS@a2<4p_0!c)O{_p+Cb$$MLs@E3ab|v=DO?B( z!(~@B14thx_+Sqa!$o82poWNoK@EyAJR+!k6iK2v&;SOimjmk&{PO{B?g9q}h6W&Z k0AdCZ+dd>CvseL$5*12Pi%ax?!gzsmeokp_o&ioZ0MG6q(*OVf literal 0 HcmV?d00001 diff --git a/test/data/win-zeroPointWave.ibw b/test/data/win-zeroPointWave.ibw new file mode 100644 index 0000000000000000000000000000000000000000..521af569cd82ad19420abc5d8a8e447d39dfb6eb GIT binary patch literal 384 zcmZQ&n40at$Urs_k*Z>> dump('mac-double.ibw', strict=False) # doctest: +REPORT_UDIFF +array([ 5., 4., 3., 2., 1.]) +{'checksum': 25137, + 'note': '', + 'noteSize': 0, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 166} +{'aModified': 0, + 'bname': array(['d', 'o', 'u', 'b', 'l', 'e', '', '', '', '', '', '', '', '', '', + '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 3001587842, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 3001587842, + 'next': 0, + 'npnts': 5, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 4, + 'useBits': '\x00', + 'wData': array([ 2.3125, 0. , 2.25 , 0. ]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')} + +>>> dump('mac-textWave.ibw') # doctest: +REPORT_UDIFF +array(['Mary', 'had', 'a', 'little', 'lamb'], + dtype='|S6') +{'checksum': 5554, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': '', + 'formulaSize': 0, + 'note': '', + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndices': '\x00\x00\x00\x04\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x0e\x00\x00\x00\x12', + 'sIndicesSize': 20, + 'version': 5, + 'wfmSize': 338} +{'aModified': 0, + 'bname': array(['t', 'e', 'x', 't', '0', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 3001571199, + 'dFolder': 69554896, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 22, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], + ['', '', '', ''], + ['', '', '', ''], + ['', '', '', '']], + dtype='|S1'), + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001571215, + 'nDim': array([5, 0, 0, 0]), + 'next': 0, + 'npnts': 5, + 'sIndices': 69557296, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 1, + 'topFullScale': 0.0, + 'type': 0, + 'useBits': '\x00', + 'wData': 236398480.0, + 'wModified': 0, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0} + +>>> dump('mac-version2.ibw', strict=False) # doctest: +REPORT_UDIFF +array([ 5., 4., 3., 2., 1.], dtype=float32) +{'checksum': -16803, + 'note': 'This is a test.', + 'noteSize': 15, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 146} +{'aModified': 0, + 'bname': array(['v', 'e', 'r', 's', 'i', 'o', 'n', '2', '', '', '', '', '', '', '', + '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 3001251979, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 3001573594, + 'next': 0, + 'npnts': 5, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': array([ 5., 4., 3., 2.]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')} + +>>> dump('mac-version3Dependent.ibw', strict=False) # doctest: +REPORT_UDIFF +array([], dtype=int8) +{'checksum': 0, + 'formula': '', + 'formulaSize': 0, + 'note': '', + 'noteSize': 8257536, + 'pictSize': 262144, + 'version': 3, + 'wfmSize': 0} +{'aModified': 10, + 'bname': array(['', '', 'v', 'e', 'r', 's', 'i', 'o', 'n', '3', 'D', 'e', 'p', 'e', + 'n', 'd', 'e', 'n', 't', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 1507328, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': -487849984, + 'fileName': 0, + 'formula': 1577, + 'fsValid': 1, + 'hsA': 4.5193417557662e-309, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 45801, + 'next': 131072, + 'npnts': 0, + 'srcFldr': 0, + 'swModified': 1, + 'topFullScale': 0.0, + 'type': -32334, + 'useBits': '\x00', + 'wData': array([ 0., 0., 0., 0.]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 3835494400, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')} + +>>> dump('mac-version5.ibw') # doctest: +REPORT_UDIFF +array([ 5., 4., 3., 2., 1.], dtype=float32) +{'checksum': -12033, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [['Column0'], [], [], []], + 'dimLabelsSize': array([64, 0, 0, 0]), + 'formula': '', + 'formulaSize': 0, + 'note': 'This is a test.', + 'noteSize': 15, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 340} +{'aModified': 0, + 'bname': array(['v', 'e', 'r', 's', 'i', 'o', 'n', '5', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 3001252180, + 'dFolder': 69554896, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 27, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([69554136, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], + ['', '', '', ''], + ['', '', '', ''], + ['', '', '', '']], + dtype='|S1'), + 'fileName': 69554292, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001573601, + 'nDim': array([5, 0, 0, 0]), + 'next': 69555212, + 'npnts': 5, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': -32349, + 'swModified': 1, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 5.0, + 'wModified': 0, + 'waveNoteH': 69554032, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0} + +>>> dump('mac-zeroPointWave.ibw') # doctest: +REPORT_UDIFF +array([], dtype=float32) +{'checksum': -15649, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': '', + 'formulaSize': 0, + 'note': '', + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 320} +{'aModified': 3, + 'bname': array(['z', 'e', 'r', 'o', 'W', 'a', 'v', 'e', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 3001573964, + 'dFolder': 69554896, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 29, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], + ['', '', '', ''], + ['', '', '', ''], + ['', '', '', '']], + dtype='|S1'), + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001573964, + 'nDim': array([0, 0, 0, 0]), + 'next': 0, + 'npnts': 0, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 1, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 0.0, + 'wModified': 1, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0} + +>>> dump('win-double.ibw') # doctest: +REPORT_UDIFF +array([ 5., 4., 3., 2., 1.]) +{'checksum': 28962, + 'note': '', + 'noteSize': 0, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 166} +{'aModified': 0, + 'bname': array(['d', 'o', 'u', 'b', 'l', 'e', '', '', '', '', '', '', '', '', '', + '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 3001587842, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 3001587842, + 'next': 0, + 'npnts': 5, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 4, + 'useBits': '\x00', + 'wData': array([ 0. , 2.3125, 0. , 2.25 ]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')} + +>>> dump('win-textWave.ibw') # doctest: +REPORT_UDIFF +array(['Mary', 'had', 'a', 'little', 'lamb'], + dtype='|S6') +{'checksum': 184, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': '', + 'formulaSize': 0, + 'note': '', + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndices': '\x04\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x0e\x00\x00\x00\x12\x00\x00\x00', + 'sIndicesSize': 20, + 'version': 5, + 'wfmSize': 338} +{'aModified': 0, + 'bname': array(['t', 'e', 'x', 't', '0', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 3001571199, + 'dFolder': 8108612, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 32, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], + ['', '', '', ''], + ['', '', '', ''], + ['', '', '', '']], + dtype='|S1'), + 'fileName': 7814472, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001571215, + 'nDim': array([5, 0, 0, 0]), + 'next': 0, + 'npnts': 5, + 'sIndices': 8133100, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': -1007, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 0, + 'useBits': '\x00', + 'wData': 7.865683337909351e+34, + 'wModified': 1, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0} + +>>> dump('win-version2.ibw') # doctest: +REPORT_UDIFF +array([ 5., 4., 3., 2., 1.], dtype=float32) +{'checksum': 1047, + 'note': 'This is a test.', + 'noteSize': 15, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 146} +{'aModified': 0, + 'bname': array(['v', 'e', 'r', 's', 'i', 'o', 'n', '2', '', '', '', '', '', '', '', + '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 3001251979, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 3001573594, + 'next': 0, + 'npnts': 5, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': array([ 5., 4., 3., 2.]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')} + +>>> dump('win-version5.ibw') # doctest: +REPORT_UDIFF +array([ 5., 4., 3., 2., 1.], dtype=float32) +{'checksum': 13214, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [['Column0'], [], [], []], + 'dimLabelsSize': array([64, 0, 0, 0]), + 'formula': '', + 'formulaSize': 0, + 'note': 'This is a test.', + 'noteSize': 15, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 340} +{'aModified': 0, + 'bname': array(['v', 'e', 'r', 's', 'i', 'o', 'n', '5', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 3001252180, + 'dFolder': 8108612, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 30, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([8138784, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], + ['', '', '', ''], + ['', '', '', ''], + ['', '', '', '']], + dtype='|S1'), + 'fileName': 8131824, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001573601, + 'nDim': array([5, 0, 0, 0]), + 'next': 8125236, + 'npnts': 5, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': -1007, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 5.0, + 'wModified': 1, + 'waveNoteH': 8131596, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0} + +>>> dump('win-zeroPointWave.ibw') # doctest: +REPORT_UDIFF +array([], dtype=float32) +{'checksum': 27541, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': '', + 'formulaSize': 0, + 'note': '', + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 320} +{'aModified': 3, + 'bname': array(['z', 'e', 'r', 'o', 'W', 'a', 'v', 'e', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 3001573964, + 'dFolder': 8108612, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 31, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], + ['', '', '', ''], + ['', '', '', ''], + ['', '', '', '']], + dtype='|S1'), + 'fileName': 8125252, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001573964, + 'nDim': array([0, 0, 0, 0]), + 'next': 8133140, + 'npnts': 0, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': -1007, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 0.0, + 'wModified': 1, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0} +""" + +import os.path +from pprint import pformat +import sys + +from igor.binarywave import loadibw + + +_this_dir = os.path.dirname(__file__) +_data_dir = os.path.join(_this_dir, 'data') + +def dump(filename, strict=True): + sys.stderr.write('Testing {}\n'.format(filename)) + path = os.path.join(_data_dir, filename) + data,bin_info,wave_info = loadibw(path, strict=strict) + pprint(data) + pprint(bin_info) + pprint(wave_info) + +def pprint(data): + lines = pformat(data).splitlines() + print('\n'.join([line.rstrip() for line in lines])) From d20d217c2d2806c32bb40dbd68c4b0aea35e5fd6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 16 Jul 2012 23:23:47 -0400 Subject: [PATCH 17/76] Split struct and util modules out of binarywave. --- igor/binarywave.py | 495 +++++++++++++-------------------------------- igor/struct.py | 181 +++++++++++++++++ igor/util.py | 53 +++++ 3 files changed, 376 insertions(+), 353 deletions(-) create mode 100644 igor/struct.py create mode 100644 igor/util.py diff --git a/igor/binarywave.py b/igor/binarywave.py index 856c85f..a79c6be 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -26,181 +26,15 @@ # share. We hope IGOR Technical Notes will provide you with lots of # valuable information while you are developing IGOR applications. -import array -import struct -import sys -import types - -import numpy - - -_buffer = buffer # save builtin buffer for clobbered situations - - -class Field (object): - """Represent a Structure field. - - See Also - -------- - Structure - """ - def __init__(self, format, name, default=None, help=None, count=1): - self.format = format # See the struct documentation - self.name = name - self.default = None - self.help = help - self.count = count - self.total_count = numpy.prod(count) - -class Structure (struct.Struct): - """Represent a C structure. - - A convenient wrapper around struct.Struct that uses Fields and - adds dict-handling methods for transparent name assignment. - - See Also - -------- - Field - - Examples - -------- - - Represent the C structure:: - - struct thing { - short version; - long size[3]; - } - - As - - >>> from pprint import pprint - >>> thing = Structure(name='thing', - ... fields=[Field('h', 'version'), Field('l', 'size', count=3)]) - >>> thing.set_byte_order('>') - >>> b = array.array('b', range(2+4*3)) - >>> d = thing.unpack_dict_from(buffer=b) - >>> pprint(d) - {'size': array([ 33752069, 101124105, 168496141]), 'version': 1} - >>> [hex(x) for x in d['size']] - ['0x2030405L', '0x6070809L', '0xa0b0c0dL'] - - You can even get fancy with multi-dimensional arrays. - - >>> thing = Structure(name='thing', - ... fields=[Field('h', 'version'), Field('l', 'size', count=(3,2))]) - >>> thing.set_byte_order('>') - >>> b = array.array('b', range(2+4*3*2)) - >>> d = thing.unpack_dict_from(buffer=b) - >>> d['size'].shape - (3, 2) - >>> pprint(d) - {'size': array([[ 33752069, 101124105], - [168496141, 235868177], - [303240213, 370612249]]), - 'version': 1} - """ - def __init__(self, name, fields, byte_order='='): - # '=' for native byte order, standard size and alignment - # See http://docs.python.org/library/struct for details - self.name = name - self.fields = fields - self.set_byte_order(byte_order) - - def __str__(self): - return self.name - - def set_byte_order(self, byte_order): - """Allow changing the format byte_order on the fly. - """ - if (hasattr(self, 'format') and self.format != None - and self.format.startswith(byte_order)): - return # no need to change anything - format = [] - for field in self.fields: - format.extend([field.format]*field.total_count) - struct.Struct.__init__(self, format=byte_order+''.join(format).replace('P', 'L')) - - def _flatten_args(self, args): - # handle Field.count > 0 - flat_args = [] - for a,f in zip(args, self.fields): - if f.total_count > 1: - flat_args.extend(a) - else: - flat_args.append(a) - return flat_args - - def _unflatten_args(self, args): - # handle Field.count > 0 - unflat_args = [] - i = 0 - for f in self.fields: - if f.total_count > 1: - data = numpy.array(args[i:i+f.total_count]) - data = data.reshape(f.count) - unflat_args.append(data) - else: - unflat_args.append(args[i]) - i += f.total_count - return unflat_args - - def pack(self, *args): - return struct.Struct.pack(self, *self._flatten_args(args)) - - def pack_into(self, buffer, offset, *args): - return struct.Struct.pack_into(self, buffer, offset, - *self._flatten_args(args)) - - def _clean_dict(self, dict): - for f in self.fields: - if f.name not in dict: - if f.default != None: - dict[f.name] = f.default - else: - raise ValueError('{} field not set for {}'.format( - f.name, self.__class__.__name__)) - return dict - - def pack_dict(self, dict): - dict = self._clean_dict(dict) - return self.pack(*[dict[f.name] for f in self.fields]) - - def pack_dict_into(self, buffer, offset, dict={}): - dict = self._clean_dict(dict) - return self.pack_into(buffer, offset, - *[dict[f.name] for f in self.fields]) - - def unpack(self, string): - return self._unflatten_args(struct.Struct.unpack(self, string)) - - def unpack_from(self, buffer, offset=0): - try: - args = struct.Struct.unpack_from(self, buffer, offset) - except struct.error as e: - if not self.name in ('WaveHeader2', 'WaveHeader5'): - raise - # HACK! For WaveHeader5, when npnts is 0, wData is - # optional. If we couldn't unpack the structure, fill in - # wData with zeros and try again, asserting that npnts is - # zero. - if len(buffer) - offset < self.size: - # missing wData? Pad with zeros - buffer += _buffer('\x00'*(self.size + offset - len(buffer))) - args = struct.Struct.unpack_from(self, buffer, offset) - unpacked = self._unflatten_args(args) - data = dict(zip([f.name for f in self.fields], - unpacked)) - assert data['npnts'] == 0, data['npnts'] - return self._unflatten_args(args) - - def unpack_dict(self, string): - return dict(zip([f.name for f in self.fields], - self.unpack(string))) - - def unpack_dict_from(self, buffer, offset=0): - return dict(zip([f.name for f in self.fields], - self.unpack_from(buffer, offset))) +import array as _array +import sys as _sys +import types as _types + +import numpy as _numpy + +from .struct import Structure as _Structure +from .struct import Field as _Field +from .util import assert_null as _assert_null # Numpy doesn't support complex integers by default, see @@ -209,40 +43,41 @@ def unpack_dict_from(self, buffer, offset=0): # So we roll our own types. See # http://docs.scipy.org/doc/numpy/user/basics.rec.html # http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html -complexInt8 = numpy.dtype([('real', numpy.int8), ('imag', numpy.int8)]) -complexInt16 = numpy.dtype([('real', numpy.int16), ('imag', numpy.int16)]) -complexInt32 = numpy.dtype([('real', numpy.int32), ('imag', numpy.int32)]) -complexUInt8 = numpy.dtype([('real', numpy.uint8), ('imag', numpy.uint8)]) -complexUInt16 = numpy.dtype([('real', numpy.uint16), ('imag', numpy.uint16)]) -complexUInt32 = numpy.dtype([('real', numpy.uint32), ('imag', numpy.uint32)]) - +complexInt8 = _numpy.dtype([('real', _numpy.int8), ('imag', _numpy.int8)]) +complexInt16 = _numpy.dtype([('real', _numpy.int16), ('imag', _numpy.int16)]) +complexInt32 = _numpy.dtype([('real', _numpy.int32), ('imag', _numpy.int32)]) +complexUInt8 = _numpy.dtype([('real', _numpy.uint8), ('imag', _numpy.uint8)]) +complexUInt16 = _numpy.dtype( + [('real', _numpy.uint16), ('imag', _numpy.uint16)]) +complexUInt32 = _numpy.dtype( + [('real', _numpy.uint32), ('imag', _numpy.uint32)]) # Begin IGOR constants and typedefs from IgorBin.h # From IgorMath.h -TYPE_TABLE = { # (key: integer flag, value: numpy dtype) - 0:None, # Text wave, not handled in ReadWave.c - 1:numpy.complex, # NT_CMPLX, makes number complex. - 2:numpy.float32, # NT_FP32, 32 bit fp numbers. - 3:numpy.complex64, - 4:numpy.float64, # NT_FP64, 64 bit fp numbers. - 5:numpy.complex128, - 8:numpy.int8, # NT_I8, 8 bit signed integer. Requires Igor Pro - # 2.0 or later. +TYPE_TABLE = { # (key: integer flag, value: numpy dtype) + 0:None, # Text wave, not handled in ReadWave.c + 1:_numpy.complex, # NT_CMPLX, makes number complex. + 2:_numpy.float32, # NT_FP32, 32 bit fp numbers. + 3:_numpy.complex64, + 4:_numpy.float64, # NT_FP64, 64 bit fp numbers. + 5:_numpy.complex128, + 8:_numpy.int8, # NT_I8, 8 bit signed integer. Requires Igor Pro + # 2.0 or later. 9:complexInt8, - 0x10:numpy.int16,# NT_I16, 16 bit integer numbers. Requires Igor - # Pro 2.0 or later. + 0x10:_numpy.int16,# NT_I16, 16 bit integer numbers. Requires Igor + # Pro 2.0 or later. 0x11:complexInt16, - 0x20:numpy.int32,# NT_I32, 32 bit integer numbers. Requires Igor - # Pro 2.0 or later. + 0x20:_numpy.int32,# NT_I32, 32 bit integer numbers. Requires Igor + # Pro 2.0 or later. 0x21:complexInt32, -# 0x40:None, # NT_UNSIGNED, Makes above signed integers -# # unsigned. Requires Igor Pro 3.0 or later. - 0x48:numpy.uint8, +# 0x40:None, # NT_UNSIGNED, Makes above signed integers +# # unsigned. Requires Igor Pro 3.0 or later. + 0x48:_numpy.uint8, 0x49:complexUInt8, - 0x50:numpy.uint16, + 0x50:_numpy.uint16, 0x51:complexUInt16, - 0x60:numpy.uint32, + 0x60:_numpy.uint32, 0x61:complexUInt32, } @@ -250,55 +85,55 @@ def unpack_dict_from(self, buffer, offset=0): MAXDIMS = 4 # From binary.h -BinHeaderCommon = Structure( # WTK: this one is mine. +BinHeaderCommon = _Structure( # WTK: this one is mine. name='BinHeaderCommon', fields=[ - Field('h', 'version', help='Version number for backwards compatibility.'), + _Field('h', 'version', help='Version number for backwards compatibility.'), ]) -BinHeader1 = Structure( +BinHeader1 = _Structure( name='BinHeader1', fields=[ - Field('h', 'version', help='Version number for backwards compatibility.'), - Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), - Field('h', 'checksum', help='Checksum over this header and the wave header.'), + _Field('h', 'version', help='Version number for backwards compatibility.'), + _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), + _Field('h', 'checksum', help='Checksum over this header and the wave header.'), ]) -BinHeader2 = Structure( +BinHeader2 = _Structure( name='BinHeader2', fields=[ - Field('h', 'version', help='Version number for backwards compatibility.'), - Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), - Field('l', 'noteSize', help='The size of the note text.'), - Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'), - Field('h', 'checksum', help='Checksum over this header and the wave header.'), + _Field('h', 'version', help='Version number for backwards compatibility.'), + _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), + _Field('l', 'noteSize', help='The size of the note text.'), + _Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'), + _Field('h', 'checksum', help='Checksum over this header and the wave header.'), ]) -BinHeader3 = Structure( +BinHeader3 = _Structure( name='BinHeader3', fields=[ - Field('h', 'version', help='Version number for backwards compatibility.'), - Field('h', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), - Field('l', 'noteSize', help='The size of the note text.'), - Field('l', 'formulaSize', help='The size of the dependency formula, if any.'), - Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'), - Field('h', 'checksum', help='Checksum over this header and the wave header.'), + _Field('h', 'version', help='Version number for backwards compatibility.'), + _Field('h', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), + _Field('l', 'noteSize', help='The size of the note text.'), + _Field('l', 'formulaSize', help='The size of the dependency formula, if any.'), + _Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'), + _Field('h', 'checksum', help='Checksum over this header and the wave header.'), ]) -BinHeader5 = Structure( +BinHeader5 = _Structure( name='BinHeader5', fields=[ - Field('h', 'version', help='Version number for backwards compatibility.'), - Field('h', 'checksum', help='Checksum over this header and the wave header.'), - Field('l', 'wfmSize', help='The size of the WaveHeader5 data structure plus the wave data.'), - Field('l', 'formulaSize', help='The size of the dependency formula, if any.'), - Field('l', 'noteSize', help='The size of the note text.'), - Field('l', 'dataEUnitsSize', help='The size of optional extended data units.'), - Field('l', 'dimEUnitsSize', help='The size of optional extended dimension units.', count=MAXDIMS), - Field('l', 'dimLabelsSize', help='The size of optional dimension labels.', count=MAXDIMS), - Field('l', 'sIndicesSize', help='The size of string indicies if this is a text wave.'), - Field('l', 'optionsSize1', default=0, help='Reserved. Write zero. Ignore on read.'), - Field('l', 'optionsSize2', default=0, help='Reserved. Write zero. Ignore on read.'), + _Field('h', 'version', help='Version number for backwards compatibility.'), + _Field('h', 'checksum', help='Checksum over this header and the wave header.'), + _Field('l', 'wfmSize', help='The size of the WaveHeader5 data structure plus the wave data.'), + _Field('l', 'formulaSize', help='The size of the dependency formula, if any.'), + _Field('l', 'noteSize', help='The size of the note text.'), + _Field('l', 'dataEUnitsSize', help='The size of optional extended data units.'), + _Field('l', 'dimEUnitsSize', help='The size of optional extended dimension units.', count=MAXDIMS), + _Field('l', 'dimLabelsSize', help='The size of optional dimension labels.', count=MAXDIMS), + _Field('l', 'sIndicesSize', help='The size of string indicies if this is a text wave.'), + _Field('l', 'optionsSize1', default=0, help='Reserved. Write zero. Ignore on read.'), + _Field('l', 'optionsSize2', default=0, help='Reserved. Write zero. Ignore on read.'), ]) @@ -311,80 +146,80 @@ def unpack_dict_from(self, buffer, offset=0): # Header to an array of waveform data. -WaveHeader2 = Structure( +WaveHeader2 = _Structure( name='WaveHeader2', fields=[ - Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'), - Field('P', 'next', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME2+2), - Field('h', 'whVersion', default=0, help='Write 0. Ignore on read.'), - Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1), - Field('c', 'xUnits', default=0, help='Natural x-axis units go here - null if none.', count=MAX_UNIT_CHARS+1), - Field('l', 'npnts', help='Number of data points in wave.'), - Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('d', 'hsA', help='X value for point p = hsA*p + hsB'), - Field('d', 'hsB', help='X value for point p = hsA*p + hsB'), - Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('h', 'fsValid', help='True if full scale values have meaning.'), - Field('d', 'topFullScale', help='The min full scale value for wave.'), # sic, 'min' should probably be 'max' - Field('d', 'botFullScale', help='The min full scale value for wave.'), - Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'), - Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('L', 'creationDate', help='DateTime of creation. Not used in version 1 files.'), - Field('c', 'wUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=2), - Field('L', 'modDate', help='DateTime of last modification.'), - Field('P', 'waveNoteH', help='Used in memory only. Write zero. Ignore on read.'), - Field('f', 'wData', help='The start of the array of waveform data.', count=4), + _Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'), + _Field('P', 'next', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME2+2), + _Field('h', 'whVersion', default=0, help='Write 0. Ignore on read.'), + _Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1), + _Field('c', 'xUnits', default=0, help='Natural x-axis units go here - null if none.', count=MAX_UNIT_CHARS+1), + _Field('l', 'npnts', help='Number of data points in wave.'), + _Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('d', 'hsA', help='X value for point p = hsA*p + hsB'), + _Field('d', 'hsB', help='X value for point p = hsA*p + hsB'), + _Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('h', 'fsValid', help='True if full scale values have meaning.'), + _Field('d', 'topFullScale', help='The min full scale value for wave.'), # sic, 'min' should probably be 'max' + _Field('d', 'botFullScale', help='The min full scale value for wave.'), + _Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'), + _Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('L', 'creationDate', help='DateTime of creation. Not used in version 1 files.'), + _Field('c', 'wUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=2), + _Field('L', 'modDate', help='DateTime of last modification.'), + _Field('P', 'waveNoteH', help='Used in memory only. Write zero. Ignore on read.'), + _Field('f', 'wData', help='The start of the array of waveform data.', count=4), ]) -WaveHeader5 = Structure( +WaveHeader5 = _Structure( name='WaveHeader5', fields=[ - Field('P', 'next', help='link to next wave in linked list.'), - Field('L', 'creationDate', help='DateTime of creation.'), - Field('L', 'modDate', help='DateTime of last modification.'), - Field('l', 'npnts', help='Total number of points (multiply dimensions up to first zero).'), - Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'), - Field('h', 'dLock', default=0, help='Reserved. Write zero. Ignore on read.'), - Field('c', 'whpad1', default=0, help='Reserved. Write zero. Ignore on read.', count=6), - Field('h', 'whVersion', default=1, help='Write 1. Ignore on read.'), - Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME5+1), - Field('l', 'whpad2', default=0, help='Reserved. Write zero. Ignore on read.'), - Field('P', 'dFolder', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('P', 'next', help='link to next wave in linked list.'), + _Field('L', 'creationDate', help='DateTime of creation.'), + _Field('L', 'modDate', help='DateTime of last modification.'), + _Field('l', 'npnts', help='Total number of points (multiply dimensions up to first zero).'), + _Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'), + _Field('h', 'dLock', default=0, help='Reserved. Write zero. Ignore on read.'), + _Field('c', 'whpad1', default=0, help='Reserved. Write zero. Ignore on read.', count=6), + _Field('h', 'whVersion', default=1, help='Write 1. Ignore on read.'), + _Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME5+1), + _Field('l', 'whpad2', default=0, help='Reserved. Write zero. Ignore on read.'), + _Field('P', 'dFolder', default=0, help='Used in memory only. Write zero. Ignore on read.'), # Dimensioning info. [0] == rows, [1] == cols etc - Field('l', 'nDim', help='Number of of items in a dimension -- 0 means no data.', count=MAXDIMS), - Field('d', 'sfA', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS), - Field('d', 'sfB', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS), + _Field('l', 'nDim', help='Number of of items in a dimension -- 0 means no data.', count=MAXDIMS), + _Field('d', 'sfA', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS), + _Field('d', 'sfB', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS), # SI units - Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1), - Field('c', 'dimUnits', default=0, help='Natural dimension units go here - null if none.', count=(MAXDIMS, MAX_UNIT_CHARS+1)), - Field('h', 'fsValid', help='TRUE if full scale values have meaning.'), - Field('h', 'whpad3', default=0, help='Reserved. Write zero. Ignore on read.'), - Field('d', 'topFullScale', help='The max and max full scale value for wave'), # sic, probably "max and min" - Field('d', 'botFullScale', help='The max and max full scale value for wave.'), # sic, probably "max and min" - Field('P', 'dataEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('P', 'dimEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS), - Field('P', 'dimLabels', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS), - Field('P', 'waveNoteH', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('l', 'whUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=16), + _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1), + _Field('c', 'dimUnits', default=0, help='Natural dimension units go here - null if none.', count=(MAXDIMS, MAX_UNIT_CHARS+1)), + _Field('h', 'fsValid', help='TRUE if full scale values have meaning.'), + _Field('h', 'whpad3', default=0, help='Reserved. Write zero. Ignore on read.'), + _Field('d', 'topFullScale', help='The max and max full scale value for wave'), # sic, probably "max and min" + _Field('d', 'botFullScale', help='The max and max full scale value for wave.'), # sic, probably "max and min" + _Field('P', 'dataEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('P', 'dimEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS), + _Field('P', 'dimLabels', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS), + _Field('P', 'waveNoteH', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('l', 'whUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=16), # The following stuff is considered private to Igor. - Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'), - Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('h', 'whpad4', default=0, help='Reserved. Write zero. Ignore on read.'), - Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('P', 'sIndices', default=0, help='Used in memory only. Write zero. Ignore on read.'), - Field('f', 'wData', help='The start of the array of data. Must be 64 bit aligned.', count=1), + _Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('h', 'swModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('c', 'useBits', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('c', 'kindBits', default=0, help='Reserved. Write zero. Ignore on read.'), + _Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('h', 'whpad4', default=0, help='Reserved. Write zero. Ignore on read.'), + _Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('P', 'sIndices', default=0, help='Used in memory only. Write zero. Ignore on read.'), + _Field('f', 'wData', help='The start of the array of data. Must be 64 bit aligned.', count=1), ]) # End IGOR constants and typedefs from IgorBin.h @@ -399,7 +234,7 @@ def need_to_reorder_bytes(version): return version & 0xFF == 0 def byte_order(needToReorderBytes): - little_endian = sys.byteorder == 'little' + little_endian = _sys.byteorder == 'little' if needToReorderBytes: little_endian = not little_endian if little_endian: @@ -431,9 +266,9 @@ def version_structs(version, byte_order): return (bin, wave, checkSumSize) def checksum(buffer, byte_order, oldcksum, numbytes): - x = numpy.ndarray( + x = _numpy.ndarray( (numbytes/2,), # 2 bytes to a short -- ignore trailing odd byte - dtype=numpy.dtype(byte_order+'h'), + dtype=_numpy.dtype(byte_order+'h'), buffer=buffer) oldcksum += x.sum() if oldcksum > 2**31: # fake the C implementation's int rollover @@ -442,52 +277,6 @@ def checksum(buffer, byte_order, oldcksum, numbytes): oldcksum -= 2**31 return oldcksum & 0xffff -def hex_bytes(buffer, spaces=None): - r"""Pretty-printing for binary buffers. - - >>> hex_bytes(buffer('\x00\x01\x02\x03\x04')) - '0001020304' - >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=1) - '00 01 02 03 04' - >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=2) - '0001 0203 04' - >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=2) - '0001 0203 0405 06' - >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=3) - '000102 030405 06' - """ - hex_bytes = ['{:02x}'.format(ord(x)) for x in buffer] - if spaces is None: - return ''.join(hex_bytes) - elif spaces is 1: - return ' '.join(hex_bytes) - for i in range(len(hex_bytes)//spaces): - hex_bytes.insert((spaces+1)*(i+1)-1, ' ') - return ''.join(hex_bytes) - -def assert_null(buffer, strict=True): - r"""Ensure an input buffer is entirely zero. - - >>> assert_null(buffer('')) - >>> assert_null(buffer('\x00\x00')) - >>> assert_null(buffer('\x00\x01\x02\x03')) - Traceback (most recent call last): - ... - ValueError: 00 01 02 03 - >>> stderr = sys.stderr - >>> sys.stderr = sys.stdout - >>> assert_null(buffer('\x00\x01\x02\x03'), strict=False) - warning: post-data padding not zero: 00 01 02 03 - >>> sys.stderr = stderr - """ - if buffer and ord(max(buffer)) != 0: - hex_string = hex_bytes(buffer, spaces=1) - if strict: - raise ValueError(hex_string) - else: - sys.stderr.write( - 'warning: post-data padding not zero: {}\n'.format(hex_string)) - # Translated from ReadWave() def loadibw(filename, strict=True): if hasattr(filename, 'read'): @@ -523,18 +312,18 @@ def loadibw(filename, strict=True): tail = 4 # 4 = size of wData field in WaveHeader5 structure waveDataSize = bin_info['wfmSize'] - (wave_struct.size - tail) # dtype() wrapping to avoid numpy.generic and - # getset_descriptor issues with the builtin Numpy types + # getset_descriptor issues with the builtin numpy types # (e.g. int32). It has no effect on our local complex # integers. if version == 5: shape = [n for n in wave_info['nDim'] if n > 0] or (0,) else: shape = (wave_info['npnts'],) - t = numpy.dtype(numpy.int8) # setup a safe default + t = _numpy.dtype(_numpy.int8) # setup a safe default if wave_info['type'] == 0: # text wave shape = (waveDataSize,) elif wave_info['type'] in TYPE_TABLE or wave_info['npnts']: - t = numpy.dtype(TYPE_TABLE[wave_info['type']]) + t = _numpy.dtype(TYPE_TABLE[wave_info['type']]) assert waveDataSize == wave_info['npnts'] * t.itemsize, ( '{}, {}, {}, {}'.format( waveDataSize, wave_info['npnts'], t.itemsize, t)) @@ -543,9 +332,9 @@ def loadibw(filename, strict=True): if wave_info['npnts'] == 0: data_b = buffer('') else: - tail_data = array.array('f', b[-tail:]) + tail_data = _array.array('f', b[-tail:]) data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail)) - data = numpy.ndarray( + data = _numpy.ndarray( shape=shape, dtype=t.newbyteorder(byteOrder), buffer=data_b, @@ -559,7 +348,7 @@ def loadibw(filename, strict=True): # * 16 bytes of padding # * Optional wave note data pad_b = buffer(f.read(16)) # skip the padding - assert_null(pad_b, strict=strict) + _assert_null(pad_b, strict=strict) bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() elif version == 3: # Post-data info: @@ -574,7 +363,7 @@ def loadibw(filename, strict=True): no trailing null byte. """ pad_b = buffer(f.read(16)) # skip the padding - assert_null(pad_b, strict=strict) + _assert_null(pad_b, strict=strict) bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip() elif version == 5: @@ -632,7 +421,7 @@ def loadibw(filename, strict=True): start = offset else: assert offset == 0, offset - data = numpy.array(strings) + data = _numpy.array(strings) shape = [n for n in wave_info['nDim'] if n > 0] or (0,) data.reshape(shape) finally: diff --git a/igor/struct.py b/igor/struct.py new file mode 100644 index 0000000..d16ca8a --- /dev/null +++ b/igor/struct.py @@ -0,0 +1,181 @@ +# Copyright + +"Structure and Field classes for declaring structures " + +from __future__ import absolute_import +import struct as _struct + +import numpy as _numpy + + +_buffer = buffer # save builtin buffer for clobbered situations + + +class Field (object): + """Represent a Structure field. + + See Also + -------- + Structure + """ + def __init__(self, format, name, default=None, help=None, count=1): + self.format = format # See the struct documentation + self.name = name + self.default = None + self.help = help + self.count = count + self.total_count = _numpy.prod(count) + + +class Structure (_struct.Struct): + """Represent a C structure. + + A convenient wrapper around struct.Struct that uses Fields and + adds dict-handling methods for transparent name assignment. + + See Also + -------- + Field + + Examples + -------- + + Represent the C structure:: + + struct thing { + short version; + long size[3]; + } + + As + + >>> import array + >>> from pprint import pprint + >>> thing = Structure(name='thing', + ... fields=[Field('h', 'version'), Field('l', 'size', count=3)]) + >>> thing.set_byte_order('>') + >>> b = array.array('b', range(2+4*3)) + >>> d = thing.unpack_dict_from(buffer=b) + >>> pprint(d) + {'size': array([ 33752069, 101124105, 168496141]), 'version': 1} + >>> [hex(x) for x in d['size']] + ['0x2030405L', '0x6070809L', '0xa0b0c0dL'] + + You can even get fancy with multi-dimensional arrays. + + >>> thing = Structure(name='thing', + ... fields=[Field('h', 'version'), Field('l', 'size', count=(3,2))]) + >>> thing.set_byte_order('>') + >>> b = array.array('b', range(2+4*3*2)) + >>> d = thing.unpack_dict_from(buffer=b) + >>> d['size'].shape + (3, 2) + >>> pprint(d) + {'size': array([[ 33752069, 101124105], + [168496141, 235868177], + [303240213, 370612249]]), + 'version': 1} + """ + def __init__(self, name, fields, byte_order='='): + # '=' for native byte order, standard size and alignment + # See http://docs.python.org/library/struct for details + self.name = name + self.fields = fields + self.set_byte_order(byte_order) + + def __str__(self): + return self.name + + def set_byte_order(self, byte_order): + """Allow changing the format byte_order on the fly. + """ + if (hasattr(self, 'format') and self.format != None + and self.format.startswith(byte_order)): + return # no need to change anything + format = [] + for field in self.fields: + format.extend([field.format]*field.total_count) + super(Structure, self).__init__( + format=byte_order+''.join(format).replace('P', 'L')) + + def _flatten_args(self, args): + # handle Field.count > 0 + flat_args = [] + for a,f in zip(args, self.fields): + if f.total_count > 1: + flat_args.extend(a) + else: + flat_args.append(a) + return flat_args + + def _unflatten_args(self, args): + # handle Field.count > 0 + unflat_args = [] + i = 0 + for f in self.fields: + if f.total_count > 1: + data = _numpy.array(args[i:i+f.total_count]) + data = data.reshape(f.count) + unflat_args.append(data) + else: + unflat_args.append(args[i]) + i += f.total_count + return unflat_args + + def pack(self, *args): + return super(Structure, self)(*self._flatten_args(args)) + + def pack_into(self, buffer, offset, *args): + return super(Structure, self).pack_into( + buffer, offset, *self._flatten_args(args)) + + def _clean_dict(self, dict): + for f in self.fields: + if f.name not in dict: + if f.default != None: + dict[f.name] = f.default + else: + raise ValueError('{} field not set for {}'.format( + f.name, self.__class__.__name__)) + return dict + + def pack_dict(self, dict): + dict = self._clean_dict(dict) + return self.pack(*[dict[f.name] for f in self.fields]) + + def pack_dict_into(self, buffer, offset, dict={}): + dict = self._clean_dict(dict) + return self.pack_into(buffer, offset, + *[dict[f.name] for f in self.fields]) + + def unpack(self, string): + return self._unflatten_args( + super(Structure, self).unpack(string)) + + def unpack_from(self, buffer, offset=0): + try: + args = super(Structure, self).unpack_from(buffer, offset) + except _struct.error as e: + if not self.name in ('WaveHeader2', 'WaveHeader5'): + raise + # HACK! For WaveHeader5, when npnts is 0, wData is + # optional. If we couldn't unpack the structure, fill in + # wData with zeros and try again, asserting that npnts is + # zero. + if len(buffer) - offset < self.size: + # missing wData? Pad with zeros + buffer += _buffer('\x00'*(self.size + offset - len(buffer))) + args = super(Structure, self).unpack_from(buffer, offset) + unpacked = self._unflatten_args(args) + data = dict(zip([f.name for f in self.fields], + unpacked)) + assert data['npnts'] == 0, data['npnts'] + return self._unflatten_args(args) + + def unpack_dict(self, string): + return dict(zip([f.name for f in self.fields], + self.unpack(string))) + + def unpack_dict_from(self, buffer, offset=0): + return dict(zip([f.name for f in self.fields], + self.unpack_from(buffer, offset))) diff --git a/igor/util.py b/igor/util.py new file mode 100644 index 0000000..7b2c34f --- /dev/null +++ b/igor/util.py @@ -0,0 +1,53 @@ +# Copyright + +"Utility functions for handling buffers" + +import sys as _sys + + +def hex_bytes(buffer, spaces=None): + r"""Pretty-printing for binary buffers. + + >>> hex_bytes(buffer('\x00\x01\x02\x03\x04')) + '0001020304' + >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=1) + '00 01 02 03 04' + >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=2) + '0001 0203 04' + >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=2) + '0001 0203 0405 06' + >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=3) + '000102 030405 06' + """ + hex_bytes = ['{:02x}'.format(ord(x)) for x in buffer] + if spaces is None: + return ''.join(hex_bytes) + elif spaces is 1: + return ' '.join(hex_bytes) + for i in range(len(hex_bytes)//spaces): + hex_bytes.insert((spaces+1)*(i+1)-1, ' ') + return ''.join(hex_bytes) + +def assert_null(buffer, strict=True): + r"""Ensure an input buffer is entirely zero. + + >>> import sys + >>> assert_null(buffer('')) + >>> assert_null(buffer('\x00\x00')) + >>> assert_null(buffer('\x00\x01\x02\x03')) + Traceback (most recent call last): + ... + ValueError: 00 01 02 03 + >>> stderr = sys.stderr + >>> sys.stderr = sys.stdout + >>> assert_null(buffer('\x00\x01\x02\x03'), strict=False) + warning: post-data padding not zero: 00 01 02 03 + >>> sys.stderr = stderr + """ + if buffer and ord(max(buffer)) != 0: + hex_string = hex_bytes(buffer, spaces=1) + if strict: + raise ValueError(hex_string) + else: + _sys.stderr.write( + 'warning: post-data padding not zero: {}\n'.format(hex_string)) From d4799633bbd12ee4e6669a50180dd4900b015a8c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 17 Jul 2012 07:32:33 -0400 Subject: [PATCH 18/76] Stub out packed experiment (.pxp) reading. --- igor/packed.py | 130 ++++++++++++++++++++++++++++++ test/data/README | 3 + test/data/polar-graphs-demo.pxp | Bin 0 -> 65987 bytes test/test.py | 137 +++++++++++++++++++++++++++++--- 4 files changed, 258 insertions(+), 12 deletions(-) create mode 100644 igor/packed.py create mode 100644 test/data/polar-graphs-demo.pxp diff --git a/igor/packed.py b/igor/packed.py new file mode 100644 index 0000000..026f917 --- /dev/null +++ b/igor/packed.py @@ -0,0 +1,130 @@ +# Copyright + +from .struct import Structure as _Structure +from .struct import Field as _Field + +"Read IGOR Packed Experiment files files into records." + + +class Record (object): + def __init__(self, header, data): + self.header = header + self.data = data + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return '<{} {}>'.format(self.__class__.__name__, id(self)) + + +class UnknownRecord (Record): + def __repr__(self): + return '<{}-{} {}>'.format( + self.__class__.__name__, self.header['recordType'], id(self)) + + +class UnusedRecord (Record): + pass + + +class VariablesRecord (Record): + pass + + +class HistoryRecord (Record): + pass + + +class WaveRecord (Record): + pass + + +class RecreationRecord (Record): + pass + + +class ProcedureRecord (Record): + pass + + +class GetHistoryRecord (Record): + pass + + +class PackedFileRecord (Record): + pass + + +class FolderStartRecord (Record): + pass + + +class FolderEndRecord (Record): + pass + + +# From PackedFile.h +RECORD_TYPE = { + 0: UnusedRecord, + 1: VariablesRecord, + 2: HistoryRecord, + 3: WaveRecord, + 4: RecreationRecord, + 5: ProcedureRecord, + 6: UnusedRecord, + 7: GetHistoryRecord, + 8: PackedFileRecord, + 9: FolderStartRecord, + 10: FolderEndRecord, + } + +# Igor writes other kinds of records in a packed experiment file, for +# storing things like pictures, page setup records, and miscellaneous +# settings. The format for these records is quite complex and is not +# described in PTN003. If you are writing a program to read packed +# files, you must skip any record with a record type that is not +# listed above. + +PackedFileRecordHeader = _Structure( + name='PackedFileRecordHeader', + fields=[ + _Field('H', 'recordType', help='Record type plus superceded flag.'), + _Field('h', 'version', help='Version information depends on the type of record.'), + _Field('l', 'numDataBytes', help='Number of data bytes in the record following this record header.'), + ]) + +#CR_STR = '\x15' (\r) + +PACKEDRECTYPE_MASK = 0x7FFF # Record type = (recordType & PACKEDREC_TYPE_MASK) +SUPERCEDED_MASK = 0x8000 # Bit is set if the record is superceded by + # a later record in the packed file. + + +def load(filename, strict=True, ignore_unknown=True): + records = [] + if hasattr(filename, 'read'): + f = filename # filename is actually a stream object + else: + f = open(filename, 'rb') + try: + while True: + PackedFileRecordHeader.set_byte_order('=') + b = buffer(f.read(PackedFileRecordHeader.size)) + if not b: + break + header = PackedFileRecordHeader.unpack_dict_from(b) + data = f.read(header['numDataBytes']) + record_type = RECORD_TYPE.get( + header['recordType'] & PACKEDRECTYPE_MASK, UnknownRecord) + if record_type in [UnknownRecord, UnusedRecord + ] and not ignore_unknown: + raise KeyError('unkown record type {}'.format( + header['recordType'])) + records.append(record_type(header, data)) + finally: + if not hasattr(filename, 'read'): + f.close() + + return records + diff --git a/test/data/README b/test/data/README index 4f621bf..7ec9646 100644 --- a/test/data/README +++ b/test/data/README @@ -1 +1,4 @@ .ibw samples are from TN003.zip. + +polar-graphs-demo.pxp was distributed with IGOR Pro 5.04 as + Examples/Graphing Techniques/Obsolete/Polar Graphs Demo.pxp diff --git a/test/data/polar-graphs-demo.pxp b/test/data/polar-graphs-demo.pxp new file mode 100644 index 0000000000000000000000000000000000000000..63e03cc9faeb02cdeeff6dcb4efb8f4f1cb220c2 GIT binary patch literal 65987 zcmeIb34D~r{XhOB;l3gQn#i>RB4~*F?6W73ef9wau?UD%p@$?SyATaY%z;6ufT)Nf zhzBN!;C-A%EeG#*vk;?WjFskT}{JOCm2y+8BpZl0NWl0dcp|FOQO_;*2ZW#OEQstPKqP|(6_bdTl=`Z&F>*>?Km@iXT*o9o0+ z0{~}zos{~LmiUyF_I9f59|IL-{qWRR>BBpH6s6BQRGgNjyxlwP9_5;Qlr?Eew`5VR zG5{ZUDGSn`vb-zG0tKCMCQmHN7 zeWaoLGNoa!HarrUm%U6mt;^Dn&g``0pxU(in}$xWqML16r^ZOSS@lUIvgDwjs(Jul ze4EyJUnDY^wfgtI$dbyjOQ~wxGOFs-DH6$2lmXqAezq@C@+oKHuY#&Qq$BKSnDy$z56m1rH8V#v8hAjXH89@OybMe)-T^!zZ|oE zX=Goz9cxN&Y+Cwhld`P45&~fkeX67l#OF9Z@51N7iO;JopL?*+eS5~0C5qAsLUl;V z&)EGX0r%|~SMpKVsB~yRp%aAzl~YebVMEQK)rV>tY7XpG_iT$FBH(@d(5Hyy=C8#>2X zsdL}gz^n8#3%k6?3SAKg!+R=vThg$kv7t-Np(X0UJqHq_=&Xc3q8>Ucal8*sy!RKr zC(E1P2WHaz2PkQ)BSq;;Fzd`lrAz+U{7yAll`>y_Dmn_LI+_7!DpW23$NJP!9TOFCVDH17~mo;EwB$AI$k)pwn z2w;vl52H5x)sjfzzW%krNTf7^PvwJ`WpzjG)oLATi}vL=M$)ot4@c7dgUpJ2vtnYZ zVgR*3Z*y72gifi7DP~7Y#ySrjzJBSoo$|*P?#s*yF2P`TY}@DWPhznjGde;fvwT)e zGD_r3loR<(a%cA-^5rYp%hHzhP{uYY=`|S{3>(!D#mt%rYF<>RX5g~ylTia|y}dbe z?Vz!d$XI{;-NDpG$?Aq0P%68wZr$o*k&L4FUd=>ya!4T}pW%bpOu~+*W>}o*v`~l515VvHTxsk}+DQ3-J3=P@)h8V6NN9Dh0v#7A?1oe zP>#KWyz#g9?%n+rf%d^X7ai3)4fV(0*EA<9>xZ(RHfmk=YJ;8c)gMfj_KGXhbWO_u z80N)FO{|lgd})?KeT`SOp;lP4)EK01OWQlxSyO*-uh|2$8gaq=DEX?YSLM9)B!H2rq1GW2iA7M|e+ zx3I!~gchPP_?x;8wTJ8K;%2ES76i8Wo4T|^CW2GU(hexC%|2R}wyc~1XEg)YHr91m zSGR7J)2zTqNF1^^56;Y9yLtEF-HDO(P#^(mSy}sOSlpMksF18rQe*H@n)out&%P;Y zFcO)CDQc`8y#GM$fx5NXYkRT55rzacly!}D=Cpx<5hMGXx{S4TbsJeJdF2K0%J0A{ zH@_>&JLHv5H7B2zz4=(3a=5WBZSxv)?q+#sHle*Ja&i zRwSvlHoNw~?gNdPn=;LjCg*&j@4b@})2JxH&xzE%yCvmCQ8ZR^N@hY>>elws1_cKX z8UW{!rJRx^4#uqwZPXN(Sqv-5=mXLO2cy`}ib;`R5HhmY4qkIiOVdppsnWXI&M0;E z;$iD*Z$qg%+(e9FFiD|d4AzpvfX;lGRCe?8P$u{0=RxdqTU1F(3LI1V)$uH*e|>s1 z(;-VS%gDkowZRawc4V(H6|9*K8CeZ?Wt)Y7Oj04MVbB1x(4;|UqCr-}fL?qdNC3#r zV&QGT*(ZfFNq|)EZB4DE`qMy7G8KcBv|1$%Ci5sHAd56kp{k@?f#FmXXR4}y-vj~H z!f5Q(v;k_}+RW@VN2ytVQZ4c<(3GyXX_^b3a@}3osX9iEOw?^VsQFR1cGCc}&ZG*t zmu|It)xE&Exo%@Gxvo;vda1A~n>w?)lv_Gh*Qj+GuGVGM-POlzg;6p>tzBDth>fx_ zGqcVdWlQvEtDK`(=TFjB><3ZuWqPH-OUF}H9KH6ezM^(}WbD|8-yEftUh7uB{LAf; z$i$MOR9%W*^{eZ5qb^ueZq`}pwP$sGd^@~#c><@k?h4i}KT>1T`Ltx+vAV7*byqYu zQZhv*7!7mBp|QclFxw(bOZD0Cd(0O>td#v}%dQ4ddYJJ9YE1c3vLJj)R;uQ-J_+&K zoCz~r2PN6Da`g^Ig-(1ygd0pK~wCeF%hKbNxx2moV>bH8zE33>p8ec*K&G9vLUfE!d z4;~7WOIvn;RKfhx+x!AUbVN}ynICJ3SjloL(K5bL>JNc5PKjtrT#4tWYOF-tzuk|Z zf6yRpfJuTR(LjRQ?M1r^HEpQZm8xQNV^#V4Q8jq1Un1P0?fJV4HC^*35H=I8>$2_C z%BhW&$$bO$WvSrZC<{I+Y z?g4)%;aBp@kV$9vCh=x37)__PM;^pk8Zb=oBYWAkvd^5Uf(%BtL2*8R{~sX+VwITm#0GXzI$FHxC-yP_yIaUwUf}9ZIpRMpJKi zRny4Czv}d_ZfHo=l%q`pY7RB*Xz)3gBx*dzzAY=B)Ht>5Yw@ zo3>{iJh`B$vgt=zD^I4%7f(IXJ59LBvQ$g)ga>AD^aJ zyGzxa*oQ7kZ)J#*rd%?4>P6G1O`D>eH{CI%pfX%>ez?lvKF585GG+Qqb+m+30uv!R zWOTS)c`kkMJm?z!{j*zHmm<7(R%WHs_gU#Q_$lG4s&Iwa)cU;(zQjSPm$C_u`J#7| zvWbGk+xSIa&-D#>?ml(rWogt0z0BX~fKO9qpv&JSktjupR!vQc((+Fl>gcB#Cef_@ z?Y}~Srl#@Ll~rX69OVV`!VdL6>f@Giib6m1vSM?YMJM>XC2ZOW;xWOk*pdiN)XI=f zBwy`TrrpXUWug83cq!0sWm=~c#WMJxdbL}bb}N$-hV~D~Nr84N(>kHZ%5=1Vv|E{W zD`OFd_K(L!f$zJOnNqN*th&nCBCW{ESYO9yWqvK?Wp*NS(4Ln`QnEcSlcdD=OX&sUhn9%-`doKn;Xg{2!p7X2?dq&FT(zAap}BDvlah) zs5M+d)sZG_+VZb-ahJD)u$EIY($jdDP94&Dn9k|+vf%C1$?}ChcC%VkR?sPfhv|j_ zuBf9GAvHNYT_`#Y1zb@dR$wX0%E;m&`la{d8}-iU%{Mv;J#9&lmwIjObpzn6^h{G` zrgc;vN*k8`Z>299PA#igP*Bn`h9<-t$|@YwE6N-b z%SwvE6{F9sDOgZW6)eU?=;xRjE}UOld_^_Npp$9JcZE%%Zp=`Y??zF~A^gN<2tS!# zR#H&mU^SJF3E>50x#czGGo854ijbHqIeKg1^1au59UFa07sZ9MUhzNs6?c}Ffqb2^ z1LcacP??aeWl%Z2rgzmczM%LQ<)c5VRi@(Smi@KL{u%ko7<{I(KB%Sl?s!l74|di4 z+|nXy+0x5;)%`R8-HspfJ+r1w$0hlya7$O^^8sgVJE5Sez%j0(>`I*PM4+yeC|z*_ z(+mQ8T)9xWsJbFjP#AVhFDYZ^G8cv`a~$(%wSf1kg1NJ%OerfUx};!XSh3|ZjV(E^ z)Yw{?n{{lY_CLW-n$i!y|0}QYWF7|L=MIM?2RV*~TCP*=*{vrt1Zx1Au3T-gCo?yh zUn&ZUimNM$k}Z#M8LP?~HDTsAr$4H<3};u9d&gamQu&6uy5Q}LyXz&hC+d4%U!;FL zXPth=%+2~^|9Vh=KK6jV;r+Yx(!brLKRo+#y>OaGzw`A2!K_L1g7LdoY?*RZRrJ#8 zccV{zb8_tSjc3K2xmxV~kvXx#WBpnfK^-_PtH-R(iF*^8Fv^7i{RQUpM}t;5hevd0W;D zh$@Z0iT?Pmj&h?)}%(!3&=_ zRsa1Z7wWMqm+KRryIn6e?$viqdq{uo_WSkqqwDln-(RWE_7&(I?is27dE2hweSdQY zmubtkJ}_o&bl9vL)D= z4jtbZJUMfW{>i!J`sx>N)(7;zTd(y!sIR~4LA@!oNxx>pTK$f!YJL3HkbZPVNBx@5 zRtIU zeRFCMpMzyne-?c2n>&Ncr$2@7JA*q9??u_M;Efk{*8!vFJ~T{UK3&z{Jd&paUay&U ziN5o2p+4Y&avg2;f}_{!H(q#?{`5oZ0Dq_6@xo1L^MF3k|)u1$@W!hCil-3XXONeL1{K2xABhTGA`@ z^h3Qv=l!v72;&J|F}#1MzlJepPZ2!!Z<_y7P~`_KIjVt zUmqR9m_zf=FhWPCO$e>KYElT}5B>JF3qsS5Ob_iHHZz2|gnC@{qtLw%{50fwEkA^L zh2{*K7dklgvd|5S7KAXz&@W%D3{5<|F!avQt3sG>=$f(1LJfah9y(>-3XJpG&|~Ya z$5?L&>04G|yf=m3+P4~GUK1+reGA6DE_7_nZ5aFQp<5&CG5$M4!`Ibe4tIr~e_ec?&W@2SuqK71N; z{BC+7T=rU|5rYJ09s`UgMFps(V?Hb@ZQ)j;~Vp~yx1pr+QUV`dwOgPW}g1L z;5$$LJ$PDVfAHh4_6Mi^?C-%xqb~%1GjV0`^j~)imYzCd%T1%thz=XNFrp3-yc1Eunc184yhJIT=e`H*6SJr#M4UeCtKbb#QKkL35^eZzq z=)+EZKwm!WA$@npd-T<3->UC9W3hg;Xp-LXxgPol`I~|p_uQFR@N{PM){zfHKe^-M z=xY~ti>>&kcWicUkJv*Gei5yiy*2vDn)0aU>nFE14O@|SMfJ4c;tW z!9DMM9yH$nIC$pxH-aC@-oGr^<&9wQ{QSS>z5bJHUwCC}-{{OmcSmpf z{LARG&km3M;?AkD8>W`U8V+3(YaDPx%zx?ivFwu;$K1Pr9P99rGxqE+4o8bZ*GI3s z@aruHo|zDQtJjX;`@ih2|9IF`t3@8eNfKl!LQz29~^M$V|jmE(Jwl3 zUpzYXwNqouo}Coy(Pu&I$eY*1DjYY&;=q4%_a(6}yXMDM%^nkbV_@&ts>@%F?s+jB zJ$2fxTQ}Ym&inni{=weUW(UWAQW@;NXJ=@!m`Q{Ec7wRU!(w2-Xli?q-*3z zmhc^d^*~2u;(JNGo*~HG@qGQ{r;8a_i+Vl#e_OBP9Zb7*JhW&Fro%t3QYeD9z9wp&U^nHJ^9CPMuUB~MynmQ(Km0pD0cdT8s_FRhENy0t0#uituNzkfFp8@X*|tmk>_W0POLJ9c30-LdM5J7VkK zxi0q6)pKG?M~sZU_w=r4&Fk*yB~LHSTRC)1aOP_t1>fw~PwzGAO#N58M(Rs1Lv%GY zL;uyAF9fHoF@ivMly}RiLv7;rEW4Hc%Z0u9_ znX#pdyT>lSeOGiuT3z(7o-xq@H~)O=*H>h0(LOyh@9bgA^X`3XQQo`i8F_uGdyd__ z;Egp*K8qDpMF?cTveIpVsR-JZ&)qS5-#!D>$AkA7plo(+xL*A zH0hl!MXi2(bg>zi-rDW@;KZBP1dn9i8GPc_&B3|-9toaO`^(_Y(|#Mg`tJIm%ds=K z@4h#J8_sw)IQ5ae!L+kK2|n|SFM`F+W5G3Vr|Tb`)>$9-lkWP)yEFB#{?$xid!UC(j$Dmpr2C+duZ`jGGBk-xgx#s@O*vNm<9S%ORms+M6301(;__{x?1mHEZ3he zSfNL*xlZqU|4Kc+ZIxd7!A<&U9oOhDopFm^HStz`$h_P2S68mrmpoFdk9c{5{^yT3 z>eqDJtUJ%XSAXvZ_v<&7Jg9qbepp&#+H>Xg*R5;V*4grHxH31vmGde)*5@zyGJeKq zZ^kRGdnbOQw_AO>TdPl>_0#%)`j^&ku2^0_=KR_9SO1`IedQ@Xk6-;@D1O;%ed4|m z=fsZ=FO3I{yW%rO{W`vI?UV5@UR@jSlsPx_uX&|+{kEOc>OV=l zqJDklkLx?VIJ7=`+3xu3l@G_?jr}Hmenx_HKCx5Oh0 zm&QvrO^MgOF(y8B^ri8;OYe+tfB5bAMftt!I}dl%Pw3pM{*SKr;@hwLRs8QWejWcH z@=84V)w^*~bcrjkf(0FwwI7G@K2-!b&hH~SE@hD9RYUk>a+Qjb(XyAmwD_WEIb>U# z)W4SAZ5#ccF1-D(SFxOrb&x$C{KEM26NilDS2Y=L+;P;n|LRYTDR1pE-u}Zj@Z^SG;We?bdC^ z*yWEKYKOH(=3lNcM*a2{W6H}98wY1RV?^qIW6ZqhY2&ua$Bo1J4;rib-DQkhxyI-pBa;sIEpUUgbg+5#T6yo6#cRT=XvyD-M8Y zcT|SOl2ZMjN;Yw+MX8?sKQ7hXzq?rRJ>@FJVHIh7e)Y5rW6jyUjH@R1H#WqE7!SRC zy3yyA{ziv>eT~mEdl~QEdb06W*OA7SQ>Pez`B|wk?U@zE$&cS?3|xG(al@gTjrALD zHUi-_#^`;w8l~BH8R1LrH_pp_#CUisem=S1xc7lOjT_2W8lS66jTc9(GB!`yWK{j~ zY2yda#EhTKjvEWZPa4V(*BjH+HO8Bp?=$W{^gCnNr*9j}1AC1rB_A4l9{A9Bwc`iI zt#=vv`Y{b{^Gye7A=f)>L`r26c`@_b(KQtNFy_qp?!mf_vdjGq_ zxDTeJjax9{OXJsn{?J%_*>0nB#y^ctuN*Pr|4tv*=emq>9}Q0%_u!GHZ%tZJb2q$$VTQO6m&!5Wl7u6kd*t?d(9zc6^`GdA3rDcY6KxVg>nl6Yi1= zDC`=C2Qes$9UO&Ph1l{jv*ng8BtrDvyvA40DN2oD%Yj}KUTbBfB$D0l`~7tV9#6qlBUD-waR zGY2sd05Q?XSOXpS1&r1#5B)U1h#K@bA zQaE_xByfPZeEGQx%I7E9(mdCMd{BXjnawr9J(^jvmjV({VXCamcBU!%CY!T~rb@+m zrYf+C5*U<1K(k4~@smsyC+3(GU>e$#g1O-mLR-NyLa8ECG9ht~N>$S$k;-rt#8Yfi zh8;#?r^DeC>n-w z&!1CW86IC;QHXtWrkgcJMIj9)&pT#LQ8-diT~akiu;P;KP8e30bn^Ih7;CJDxYCBZ$g`-eG`hU2F|SFEL_4A{I~40XoR^5FbkTp=*jW(rPkrI8r#ux zHUAq1+TDtQMl3#ee04>|xPp0#T`b;RX%59z0v=ZY2T}pMKYo5eMWw<8?j4kzF?pj2 zYDT!c1P6WqY=T+C`wq&ZPt>jH);5=<0*~anijDq%An?aB-i+wE zz(2u1>s(Xj!!lJ?W;;_O>F!f;JIAp9JAr?Te&zpzu>Zg8R}yQwl=Eo!D;FgK|JMCV zWmSbzQPGllPg7jUL-onjRI{V0Y%w+U7Jw-=HGsRV${fdc@oW6WbFj{D1ydTHUiM{u zm)mc>DlKjWL%V_+Yk}~Y+P(f(mw}I6lo7I4T|B2G9I3LF@Smxj`ATk_nBOj*Q&m>Z zgL2)E3Ipt*~lbbVh);jv!+@Cvctz+$`HRtfGb@aKp z9bd!RG4E@-WGvt~faZ3?Le>hi9iNIa<0ccTxg9SrtR3?v_9Uft;mT3|8TGI0Qh%(S zJxQC}+4R>OU5L|TbZN0aA z+)ktmYllR+eZ%H8;GHt|-L?5E#9AbbVx6DN{XY zc`8dh-L%}v-Hw+OhXaf7F%AdQGY9+YgsE>A6c-hhgi~K}=OOi(0XhmwiyUUD6Ey+) zrTX!E6#MARF^dBDU>4(dj#&wa90$QC>j0XlqM}5TWQkEyf*U$WL7Bs>q+wJ!C{q+J zN)$C48Gf$GnNC+|o~FS|Of&I?CSWa3&8oRqsxBVW+$H;N(zdLMNSfGhVAM_a*hfjy zVT|K!BFNd8K5K-LOoOAhYt)GiGr>5<6PA)B#2oU@N>O;;n2LfThLB}nDYMF8IKbDI>5!g1&4HWMY}*t4IYyg+ zJ-bdXK!*K?<=8caCAbLp<4UU>YS*skoa2~VU0hN`cIHY(o!s2qo?RytS0eec$U(z$ zsVCN^!7FL=cXL);ey`Ze9II@U!?pRPz0qZq_V$+edQ(%5I zLq|&iVo=LB79rC2D~pS;?Y0=4E5(@)vsDh^`WfF#X&Kwq$#e0=1vTbs*V(fx9`itp zXAs+U&bfIm2O)8c0(HF}zh8B`{eggAbA!dXs>kKSpPKRpysFnNH#7&~80E?Z$32=? z^Ljk06D?8T3pjm#&7^p|nDq7v-mMCKLdIYu~hoKO^(&owfK3g?Q2 zWZlpm*V^oXCJC}Ea=6^NsvCR*`+P2!*O%kXb^DxdpI3GHJYLP|g1D$Ti`xbRgwv_I z++NKS@VfmjAPNM4)1!HuKA+nUDbMx0T>+Qo^ZI>Gk1q$AC)MY5y90io*Xi|Z1mX2* zXaN|_=kwvQ@&erEtBE-PE?1gPZ=xV=74Ks6yiKcb;q!>mNM=KYG;R?7t9uI*7l~48H?^pVL9uQB>4FI3l?+>^@K3@)uh|}fvsR5VE74W-# z1mX2KL47C2rGjZLvMefe$>jqzyk0-3N~M@R{<2mr(7caCZ7u7zuQ*)9)cnN|=;qmxE&wvwW!<-Nqzu&2GnF26a@hA;{||#4@VpVc12$qJ2$GGc zNx~?It0d5}#7io{6h+h?NxcLG3Z?;*G$(F0lL<FCUYMsGjXX@iMg9tw#tY*H>+W;HDLP@OV9g1_ z3!~$Mb4Nf$y`TWjL{ni{128270b_|E2}Z?_0323Xg9XC;F*2WDB>>_RX5)q6qF2~l z2!^V;Vd|Z*-iU^CePm8y#=S1Ti`i`-{25Fee5MN~6c8G0o7+S2ncEEu=z)!NA}Uki z|Gc6}1-O&UC)%rUHbCelyN@v;DnM`n>N#l|ZbBA-V_*=77UBNkd{wA6K_Kb_c`y%{ zP?%-fpmGIr128_|gHQ7?Q|yPehkF9gK~stypa|fH6YgGfdoWTzL;{NlFCq>VVKxD% z7zn!7AR7Tdz@uOo9zUW9xOd2)AGX?wzm1BJLRHD7!twjapCc}CL7WJ}1R!0o_)fEzA z9uNaE>V=!uKz@RNqoa@yLqqsS5D)>M22T%{tfGlK7XnDb^njHCR)8oDaTx>%&J4Z4 z+=~T56A7tJx6;6bp-Yn!h+#u6iJ&$l*F>fWY?Bx(uwLYWji~U#B#WCQkTxPK%Csc( zf?ANW$SMSZ5|vC+LqSm`RpvF{#xy`2MB^cu7r_AWCW?iYk`c37If3YQMo}=EHilI) zy@GL;%(sn!=Usr{Bs6jXq8kzX5g<5^m6#c2tdJDU4L}hQb7C#+gh4=HN+DtZGK&Bo z-c0l5BCvKL>ck4g35^FO{2m|N0j#a+@_NkWMG_kpN^k_QK`>sl?DE2z!{Wh~IuTiG zaK;E}{m9wCdQ(*E!4e89cOMobu%^gEuq81Ynh!%maE)ja_%uJ(Uw*d-5jjRdG{Mkd zYJ6~?h<1Sx9+V~uR}rmff@ zQ(R+^m`5)*me5@4dLedb!YM?kXVz4+K zCu}xiMMRT0(uHw25%a?wBBq9^rj>w(h#j*IoHBYV!9 z2gx!5!h?7mi5SGvi0sLB!Xvnm;zGcTg$wfu=m9S;g@X*j4;~>m17;veB@$gUa9E!R zCXm8M1o#v!Q+|b3Fkk>231)|N4*GN>K&R{#1X4wwhGm{$epx07G7bV$@)F^_hXzFn z8u2^4tk^(8DRn9hOBlE`HFCZdQrHqpBBw-f-dV{^lh`USUgUs{ph%*`d0SZ+ZNya) zXhGy!y6|X>&2Njf#i1Pkh7Pt2Udd$ zZI(^05b6O!3>V@J$c6f`gM^q9MFfHLEK(q_2^5)<(M3pxpbUYU&qw)cI1ohFxU+{4 zl6t)e#Bvy$2uKDH1kAVwv9VN5=(q! zjwmmWObkpWGlg)}=p18$X{7iO0WyrEPeqh(8c&vT#-bl74$#(R0Y%Aj5qCf*Y~-o%@PGhbiY)L$DG+ium>H642w;#?pfmuy1YCoLj0OaQ`813I zq(JHjTY>}uXO2#g4TIl<)I(8_=Er&l5Fk83P@bKo$dIpK{uWG!<3y?oSXovR%L^~! zYJ@=87$MC;LJ3(#$PYXMMoI}VtVw9C0OkV+bPF%|6KpKf76Jm<073}NJ%A|(AQ^}R zFl;0~FphxNB@!J@kaGQQ@CcJ*@IJp2lt8S2kzi(s#36e~aAEm_3d#q81K=Q*9q^uP z-v)~kB;c?zgCk=QparGauoi)9rSuOxm zVM8v7pf)6zm?E$Z#)D_9z+P-LuoE0 zu%w|~Z8w$@$Rv7^f5FJv2?5jiV;3IbK5QFh@7qlFSa?8g4DfKIa3hz$rM90zcTmR81K`*JQZ$$e z>@ap#;9!uAq3D8g7Ubt>!^8yW{s{uTxalukHX1o0Fe(ZbUU0y5>J z4Q+%=kZ~-K0Rd7_B!goV*vKFVa>n2)*6?g+L@KapLjIjYDs(H3OUSTENirf9M+#eF z3FIUN<_K=X5+QF0ZRFW1Fkaw*ouEjf#N{<@#1Up(kXJ#VC6SkvLR20ENt6uX)Jsx8 z8x<9l7@W0ccx}uA`8BKEknIY@k2gGmX%ceMl6ezIPPqf#97+b0H>uEG$><76bz8$N zn0Cqd+qeh8fry?+^i_$OnaO-%ohD}##nzIiv!q0&g*mcXHs;mZ0y5QF!lg|!V8z8! zXnbbCwB=U&Zr#d|)2AXiB)2Y-gG=G!Y+YG-s-*eMA03rW6}NHbkAss=@A0SdoFiL@ zZazNPI$U#_IVlv#G2)nj3n!}BaVz+B7-tRpCXhASltIRYR^0HjaO?0P*qEgZB~pGk zx8-tTiwDO# z3s2h2L&w$~q=bB7=L%=J;H@zfGZ~KDE+z5c-_RcxdPsQs=}eVB2Qo()Rf@8ZH+A`1 z8!WAJ5R=mxR5&B}c`Q+of}tEXoF{ha$>L#QNr&Xfdm$2}oiitvyNC<0*^h&&sogI4 zMLP9>903mgAw7(dVEsyIV~h>7hI7WTHJtUp5hUycp*6ZgsuxKlFan7&FE?|XQ1DPv z0n1n%WuPQJol`?JhrkY5PJ}iXJQk){Ey8wD1{6jN!5C!)D6ODkOG#qWe^5a4V`o3z zIO~V3I8p)>YawPtqJvI2f*znD!bmeJZW{~`Na13YjN~e9EwcPE*omMR(I`lr<3_B6 z<1XMH7TiEj5iKHBFdDfA&{QJ`w{Au*z>?jJ;#mhUOEwiR1O_$VG%8fExtlGFjIn{cnzDW*vLa7iRB`YjPO7;&`E#* zu);!k>4C%(M-ieTum{T!U*YTsm;y(OZ2`nvkVZ%-OTgjm3e*ARNNo`WZ44q-M@pS? z6%aXE4L~W8$nvOyuL9f2ebTlmf_oObB7uQppYY4rSs}52m_SBpvx1J*AZ36lV+RwX z!Y(a@oR&U_(m_z_hlQqS2hUBXKoFXemoif%;1Ff`K@Z3?Tpr8;B57ozu*-?K8M;PE zF(?M+kD&ts(}XPI)D4{xW#K5sfpi)>S;f{FU9F@EeD~QI6#FI9pZG6=(IHeIiu}hI%Y(=j!YU8FC5mwsG-Vq zZW-HMP=1UFaW||fB7d9*1vA(&E;?`v&eNG(tT7-uAT=1L!lWTRgaYI&v1+4pyrczo z1ws?#oH(3Pr{b{e2F}k6!eJ$Z9i;?PO2oEdhrlq2r2^9h@*_}}ghUb}$c$CcBvG^x zRzaL${ds|xRDw=P%jzR3n52R*0-};yl~`1HJDUJu=8cA6RwTnCnI^=vg6-mrn?Q5g zOB9WvU@j$tiiHhta&3&VV76@xyI}f3bBlBE<;dil>O&v084n?Qq9V}apSs-r$bAoJ&)oqhQfciSgHXz^5ve&dMMot+S z40svlb+Dg}+zc5CviGDe*z89tr^h$N>d#Gbc4uSZ7+y!mJ`(g5e|kO_2v(5uh{>B0r?bFchS0C?X=e ziO>(}XYifwHUzAvRA6Iarf~cQbim1ccmre`VHxNy2u4Vu86ByoSP(}{;i3`$VCjdW z0*D4-Vtrij1p7&Mfe?wp576-##OfF-TrC_01vfC*2)Gb@&;<<&8bKPwk8nhoXaLuK zQqz>1%DAmfMbuVQ(v)l_ZJiRVgD`0;HXCva4B^Sml7AdSEZ8Jbkc0(kElw09VXCe8fK)pWETsf0rg&m5et(F+75CjOe#I+rsm_{@N=ZiCQa~5x5_jYhHg6_M9MAy- zU$958+yU%|<~S*arIX(Xxey~GrXPZ*IW^zqquyG=1(Sow7cm}WgU*Q4aYLX+#EH}= z)(=BQsmF&2s!9i;=vWTILpteB8>v|TVjB>r?XjyoL_j3v3R);2E6679MpTB4T(-Rl zxrLj-#sX{!!VX&Pz?TeBJ(zZL1H_J4XhQC>TZ zLJeWHu}G#Em@gRO=6aw#R}LYZ7vJR&FV}-KWNA`EeAGii44S2KVg>Givhu{W+{>?= zSyqnEIrDMz*Sz6r)|`j$J#l&cf`Y<|GMihxxI_G1+~S3?hHIc-*e!?HfVi}JPu}D8 zG~eSz2J3(49xqvScz;L?jx}IqhQs#I4;)^i-2vS0Wh~(C|5~r{BG&_#gD?n6M9PFj z9*iQL48+0WC9kLb93qF*kq@@WEVnfLV z&kM1{d2?&AF!l4v73zQ-)q-o{=Wh(+M zG8ZT%0pNL|5ylpE5bfJW37`|k$@AA{<|q<4-0IW`T;MQ@Y~(=TF(GYHzwI$d{fHQH zSi6>V9F-!MQ6$NN$Yw$+5X2%(s1*3jaPsC@Vl?3tIM*sp8(|P-N3tH)RW!O2r+z#$ zZ)4&jlWinH;!tb4j!}v%J}yZTJK;$wGFT=B^?Urz4A7!x+R1>xUK@XFS??Ujg%Wz+ z2jc#m#q7W8|^0#ev-hbcW10 za!j;VgY$M12yH3^C_#msI5wOBL&qSI^2V_dobI8RqHSJ5Z*Yngr*APNbciFpz=Noj z?n2`Tsuww5lprQYF-03h1xy=P6{r}gD>~+jPNB4{DP3qmq90TOC=CINKitwmK8p_3 z&`hwIL66V?9y-S=UUon|CW?JWB)4$Um(3I#C^)V`2?b;{kiVcUA&P9UVTBz8Y#!Q| z$gO0|{8;RUOBZiw@8cGq9X8 zy1ew-iMXT(ys{A2K@a>ERR+3mt5|%m1jTpjW4QR~5EQRk1SF7!jEzJWfxHAG(^udV z0m(tVz;FQBXqO-?1SH=6$G`=#BPdDR1XUt0D65;G1_UGPfuK(WBoxWb2uMJV5?yUZE_;u$E3y2x^BA_#^gI|L&Lm?VTQHVs+zc!3U> zP`unr%HcDi1a+fAMl4B{XncZ`LXo6P42Z3T8v8$r7W zGsi+V&0AD2f)b>GploG{Dg>3H=Cb0oEz@WxDisw8+CfEvR<%%M(@@Y^YA7fc6-nC1 zPlpMkFlm|eqS66zf`T@rwNXcoO<0!MGgDA5f)cDQitL1mv)}lmi+~c;3q^wTCbUo% za!w7AYi8JZ1tV;{Lt}bK9?b??0%ing(~*j|lo)1S+r&xXkpToHs2j-05s1MEw6-TT z$2JyW+Nhu)0rnawc(|1K2&!ePf1*^|3ALzLfU!{zLDc}nm&k(8W1$dE>-t{_-Cq}pilr3B!Mt-;=+P>L8JgAsS{Ay zD4nQH1jXr`Omj)Ks12`xY&m9WiS-$++}PTU#tVmr{3hms+$Rohp#pK#Yjh0abOjf z2J!Ghv4p`$t!yy!V*ws{ylp2-Y-s~rG96`F%lwd8CUaHhyd)hq5+v>{2y$oybgrE6u8|fFc2#9#aA~h155fF!* z0SQ`8K$2>SGXan|U$Qh*ESVafp3JL>RF@b7M1pon63+9Rp_Of@C^>{zQa4cs35b(a zkdhZ>vp3N<2ui`$9-{6b9At7Yz!K#|P!#dKvW9|U6OgbEDGi^s<0--V69|EJJWb91 zcuf=7Ky9olg~S_mFDo7|1Prq%1ZZe0K~yd%5{g9eLy@3KC=msjfCLvsP@GOMg%fp& zplmdbYsdnAJM@A!(I8}_9|W>pcdS0 z$lut_Mj(vn%UIU}iR~#YsFbbVi5g|gc2Q`OG9w)g;Kh&8o!2J{Qh74@57uX2Ji ziRxvu(iJsJ?8#I=L9qZNYac%`S_;@H9~BF}4720)h@GjBl}%71TZ1TS5r70aKyRWF z0Z0%i0EuEJiY75d)FHrddM2B7NuQ_*uXR{qS=!Np3+5-e)%1J9J0}XT?E=HjdE%1n0i76vCBt zEUD;eyudJ`XbH}e2qkhOz{sj3 zbWSj$F7cC~xJ5Xk13`qQP_bxJL_z{apti^mhDYF<$XN4?6vur*@?~*B&%8*9LT4i7 zB~!Hhh`eodiq`^M5G5c%tEh}czC17-jtmTbi)t!&3m{Qx2#6m)fJC(+AW^kMwILXe zx^{X4Af8PE3($Al+y{`YsHrgab%)NEnnLz62z=3jz`}Owt~}2>N8x${sbZ zKK6r@XTnd{R5D(hfXSqlpg|}X2rY6#!ghtRLTa^$rNmebf6?tU=v5g*yx-QaO zoDvK1_*t7eB@s+gDe~L`;Q_;k5)}Oi3CBd%71Mk%iLO!o-+x#P@iM_*%j|?F%0P1P1QIMFu{nny{6`y-C#lrR zFq`T`6eKm{#Y=9;@^eye*2n$Z3?>tS!5WNedukzdyRSX9U`bE6rxr>UQC<@&BCL2R;FII?_S6E~6XO?l z?WqMkFAd@%EPL$MM&>HP23TB2wWk(R*#)AOklhm3;q9pf>ki%bFts2pv}hTXcxC|} zE{0%Gl-pE+#%oPVa@k~BE(1ebc;b9hd{39MZfVDn7APYUxZD?#i zHoKWgljmn9PoBdk6JFvCM|hwUSkoFh7+jgxJU#;9dCegdI0y0sR=NZ!~U!?ZOAv&!|jyyqI&T~eK5=uPdtu&NbV9R%21tpzs#Ya=`_|sSPJZ>DW z!{e#V^f8xYwG0SJbb6Ez?u6i3&j3Pmxd2bg#HlMN7+gDp;0icwZT8LWGDw|4X4~qX zKw9eG+-Vf2j}~~2x+Onu8L`}R@rfH#s}EuO z)_WZ-JnYUQhxEw4n#-yytMVMK9Lw|j5}N?*k$A9Z^Z*F_%@RDO6uTAd0G|hs+RL>( zL@xRKzyAOkp4z82KaK9&AVTJI2zbULYnuoXgoP?rLePc;Jw~;e66W&;iBF7D*geJZ zKRg~BZ@3FaxmcmVe6*jyfW-3`tzAhSIdg^tgl8LC*kyfmqtqv)P#gvRI2}(U>x`{( zU`5Q-RGdGaS=S;aPNJ-s%yZzWkUhKo EKaBfi=Kufz literal 0 HcmV?d00001 diff --git a/test/test.py b/test/test.py index bd508c0..efe7019 100644 --- a/test/test.py +++ b/test/test.py @@ -1,6 +1,6 @@ r"""Test the igor module by loading sample files. ->>> dump('mac-double.ibw', strict=False) # doctest: +REPORT_UDIFF +>>> dumpibw('mac-double.ibw', strict=False) # doctest: +REPORT_UDIFF array([ 5., 4., 3., 2., 1.]) {'checksum': 25137, 'note': '', @@ -40,7 +40,7 @@ 'xUnits': array(['', '', '', ''], dtype='|S1')} ->>> dump('mac-textWave.ibw') # doctest: +REPORT_UDIFF +>>> dumpibw('mac-textWave.ibw') # doctest: +REPORT_UDIFF array(['Mary', 'had', 'a', 'little', 'lamb'], dtype='|S6') {'checksum': 5554, @@ -106,7 +106,7 @@ 'whpad3': 0, 'whpad4': 0} ->>> dump('mac-version2.ibw', strict=False) # doctest: +REPORT_UDIFF +>>> dumpibw('mac-version2.ibw', strict=False) # doctest: +REPORT_UDIFF array([ 5., 4., 3., 2., 1.], dtype=float32) {'checksum': -16803, 'note': 'This is a test.', @@ -146,7 +146,7 @@ 'xUnits': array(['', '', '', ''], dtype='|S1')} ->>> dump('mac-version3Dependent.ibw', strict=False) # doctest: +REPORT_UDIFF +>>> dumpibw('mac-version3Dependent.ibw', strict=False) # doctest: +REPORT_UDIFF array([], dtype=int8) {'checksum': 0, 'formula': '', @@ -188,7 +188,7 @@ 'xUnits': array(['', '', '', ''], dtype='|S1')} ->>> dump('mac-version5.ibw') # doctest: +REPORT_UDIFF +>>> dumpibw('mac-version5.ibw') # doctest: +REPORT_UDIFF array([ 5., 4., 3., 2., 1.], dtype=float32) {'checksum': -12033, 'dataEUnits': '', @@ -252,7 +252,7 @@ 'whpad3': 0, 'whpad4': 0} ->>> dump('mac-zeroPointWave.ibw') # doctest: +REPORT_UDIFF +>>> dumpibw('mac-zeroPointWave.ibw') # doctest: +REPORT_UDIFF array([], dtype=float32) {'checksum': -15649, 'dataEUnits': '', @@ -316,7 +316,7 @@ 'whpad3': 0, 'whpad4': 0} ->>> dump('win-double.ibw') # doctest: +REPORT_UDIFF +>>> dumpibw('win-double.ibw') # doctest: +REPORT_UDIFF array([ 5., 4., 3., 2., 1.]) {'checksum': 28962, 'note': '', @@ -356,7 +356,7 @@ 'xUnits': array(['', '', '', ''], dtype='|S1')} ->>> dump('win-textWave.ibw') # doctest: +REPORT_UDIFF +>>> dumpibw('win-textWave.ibw') # doctest: +REPORT_UDIFF array(['Mary', 'had', 'a', 'little', 'lamb'], dtype='|S6') {'checksum': 184, @@ -422,7 +422,7 @@ 'whpad3': 0, 'whpad4': 0} ->>> dump('win-version2.ibw') # doctest: +REPORT_UDIFF +>>> dumpibw('win-version2.ibw') # doctest: +REPORT_UDIFF array([ 5., 4., 3., 2., 1.], dtype=float32) {'checksum': 1047, 'note': 'This is a test.', @@ -462,7 +462,7 @@ 'xUnits': array(['', '', '', ''], dtype='|S1')} ->>> dump('win-version5.ibw') # doctest: +REPORT_UDIFF +>>> dumpibw('win-version5.ibw') # doctest: +REPORT_UDIFF array([ 5., 4., 3., 2., 1.], dtype=float32) {'checksum': 13214, 'dataEUnits': '', @@ -526,7 +526,7 @@ 'whpad3': 0, 'whpad4': 0} ->>> dump('win-zeroPointWave.ibw') # doctest: +REPORT_UDIFF +>>> dumpibw('win-zeroPointWave.ibw') # doctest: +REPORT_UDIFF array([], dtype=float32) {'checksum': 27541, 'dataEUnits': '', @@ -589,6 +589,110 @@ 'whpad2': 0, 'whpad3': 0, 'whpad4': 0} + +>>> dumppxp('polar-graphs-demo.pxp') # doctest: +REPORT_UDIFF, +ELLIPSIS +record 0: + +record 1: + +record 2: + +record 3: + +record 4: + +record 5: + +record 6: + +record 7: + +record 8: + +record 9: + +record 10: + +record 11: + +record 12: + +record 13: + +record 14: + +record 15: + +record 16: + +record 17: + +record 18: + +record 19: + +record 20: + +record 21: + +record 22: + +record 23: + +record 24: + +record 25: + +record 26: + +record 27: + +record 28: + +record 29: + +record 30: + +record 31: + +record 32: + +record 33: + +record 34: + +record 35: + +record 36: + +record 37: + +record 38: + +record 39: + +record 40: + +record 41: + +record 42: + +record 43: + +record 44: + +record 45: + +record 46: + +record 47: + +record 48: + +record 49: + +record 50: + """ import os.path @@ -596,12 +700,13 @@ import sys from igor.binarywave import loadibw +from igor.packed import load as loadpxp _this_dir = os.path.dirname(__file__) _data_dir = os.path.join(_this_dir, 'data') -def dump(filename, strict=True): +def dumpibw(filename, strict=True): sys.stderr.write('Testing {}\n'.format(filename)) path = os.path.join(_data_dir, filename) data,bin_info,wave_info = loadibw(path, strict=strict) @@ -609,6 +714,14 @@ def dump(filename, strict=True): pprint(bin_info) pprint(wave_info) +def dumppxp(filename, strict=True): + sys.stderr.write('Testing {}\n'.format(filename)) + path = os.path.join(_data_dir, filename) + records = loadpxp(path, strict=strict) + for i,record in enumerate(records): + print('record {}:'.format(i)) + pprint(record) + def pprint(data): lines = pformat(data).splitlines() print('\n'.join([line.rstrip() for line in lines])) From 4e9f269977ac7c8a690d1a03b521715876329811 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 17 Jul 2012 07:49:06 -0400 Subject: [PATCH 19/76] API cleanup for binarywave (loadibw->load, move checksum to util, ...). Also moved byte_order to the util module and renamed a few functions internal to the binarywave module so they start with an underscore. The loadibw -> load (and saveibw -> save) change is because from igor.binarywave import load gives you enough of an idea about what you're importing. If you want to keep it explicit in your client module, use from igor.binarywave import load as loadibw which we do in the test.py module. --- igor/binarywave.py | 39 +++++++++++---------------------------- igor/util.py | 24 ++++++++++++++++++++++++ test/test.py | 2 +- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index a79c6be..70af310 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -35,6 +35,8 @@ from .struct import Structure as _Structure from .struct import Field as _Field from .util import assert_null as _assert_null +from .util import byte_order as _byte_order +from .util import checksum as _checksum # Numpy doesn't support complex integers by default, see @@ -226,22 +228,14 @@ # Begin functions from ReadWave.c -def need_to_reorder_bytes(version): +def _need_to_reorder_bytes(version): # If the low order byte of the version field of the BinHeader # structure is zero then the file is from a platform that uses # different byte-ordering and therefore all data will need to be # reordered. return version & 0xFF == 0 -def byte_order(needToReorderBytes): - little_endian = _sys.byteorder == 'little' - if needToReorderBytes: - little_endian = not little_endian - if little_endian: - return '<' # little-endian - return '>' # big-endian - -def version_structs(version, byte_order): +def _version_structs(version, byte_order): if version == 1: bin = BinHeader1 wave = WaveHeader2 @@ -265,20 +259,8 @@ def version_structs(version, byte_order): wave.set_byte_order(byte_order) return (bin, wave, checkSumSize) -def checksum(buffer, byte_order, oldcksum, numbytes): - x = _numpy.ndarray( - (numbytes/2,), # 2 bytes to a short -- ignore trailing odd byte - dtype=_numpy.dtype(byte_order+'h'), - buffer=buffer) - oldcksum += x.sum() - if oldcksum > 2**31: # fake the C implementation's int rollover - oldcksum %= 2**32 - if oldcksum > 2**31: - oldcksum -= 2**31 - return oldcksum & 0xffff - # Translated from ReadWave() -def loadibw(filename, strict=True): +def load(filename, strict=True): if hasattr(filename, 'read'): f = filename # filename is actually a stream object else: @@ -287,16 +269,17 @@ def loadibw(filename, strict=True): BinHeaderCommon.set_byte_order('=') b = buffer(f.read(BinHeaderCommon.size)) version = BinHeaderCommon.unpack_dict_from(b)['version'] - needToReorderBytes = need_to_reorder_bytes(version) - byteOrder = byte_order(needToReorderBytes) + needToReorderBytes = _need_to_reorder_bytes(version) + byteOrder = _byte_order(needToReorderBytes) if needToReorderBytes: BinHeaderCommon.set_byte_order(byteOrder) version = BinHeaderCommon.unpack_dict_from(b)['version'] - bin_struct,wave_struct,checkSumSize = version_structs(version, byteOrder) + bin_struct,wave_struct,checkSumSize = _version_structs( + version, byteOrder) b = buffer(b + f.read(bin_struct.size + wave_struct.size - BinHeaderCommon.size)) - c = checksum(b, byteOrder, 0, checkSumSize) + c = _checksum(b, byteOrder, 0, checkSumSize) if c != 0: raise ValueError( ('This does not appear to be a valid Igor binary wave file. ' @@ -431,5 +414,5 @@ def loadibw(filename, strict=True): return data, bin_info, wave_info -def saveibw(filename): +def save(filename): raise NotImplementedError diff --git a/igor/util.py b/igor/util.py index 7b2c34f..55c015c 100644 --- a/igor/util.py +++ b/igor/util.py @@ -4,6 +4,8 @@ import sys as _sys +import numpy as _numpy + def hex_bytes(buffer, spaces=None): r"""Pretty-printing for binary buffers. @@ -51,3 +53,25 @@ def assert_null(buffer, strict=True): else: _sys.stderr.write( 'warning: post-data padding not zero: {}\n'.format(hex_string)) + +# From ReadWave.c +def byte_order(needToReorderBytes): + little_endian = _sys.byteorder == 'little' + if needToReorderBytes: + little_endian = not little_endian + if little_endian: + return '<' # little-endian + return '>' # big-endian + +# From ReadWave.c +def checksum(buffer, byte_order, oldcksum, numbytes): + x = _numpy.ndarray( + (numbytes/2,), # 2 bytes to a short -- ignore trailing odd byte + dtype=_numpy.dtype(byte_order+'h'), + buffer=buffer) + oldcksum += x.sum() + if oldcksum > 2**31: # fake the C implementation's int rollover + oldcksum %= 2**32 + if oldcksum > 2**31: + oldcksum -= 2**31 + return oldcksum & 0xffff diff --git a/test/test.py b/test/test.py index efe7019..f52df5d 100644 --- a/test/test.py +++ b/test/test.py @@ -699,7 +699,7 @@ from pprint import pformat import sys -from igor.binarywave import loadibw +from igor.binarywave import load as loadibw from igor.packed import load as loadpxp From ead1da8aab6d15db20adb1434467cc7f0846778e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 17 Jul 2012 08:00:03 -0400 Subject: [PATCH 20/76] WaveRecord now loads waves as WaveRecord.wave. --- igor/packed.py | 15 +- test/test.py | 588 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 592 insertions(+), 11 deletions(-) diff --git a/igor/packed.py b/igor/packed.py index 026f917..ebcbdfc 100644 --- a/igor/packed.py +++ b/igor/packed.py @@ -1,5 +1,8 @@ # Copyright +from io import BytesIO as _BytesIO + +from .binarywave import load as _loadibw from .struct import Structure as _Structure from .struct import Field as _Field @@ -37,7 +40,15 @@ class HistoryRecord (Record): class WaveRecord (Record): - pass + def __init__(self, *args, **kwargs): + super(WaveRecord, self).__init__(*args, **kwargs) + self.wave = _loadibw(_BytesIO(bytes(self.data)), strict=False) + + def __str__(self): + return str(self.wave) + + def __repr__(self): + return str(self.wave) class RecreationRecord (Record): @@ -114,7 +125,7 @@ def load(filename, strict=True, ignore_unknown=True): if not b: break header = PackedFileRecordHeader.unpack_dict_from(b) - data = f.read(header['numDataBytes']) + data = buffer(f.read(header['numDataBytes'])) record_type = RECORD_TYPE.get( header['recordType'] & PACKEDRECTYPE_MASK, UnknownRecord) if record_type in [UnknownRecord, UnusedRecord diff --git a/test/test.py b/test/test.py index f52df5d..2c8c60e 100644 --- a/test/test.py +++ b/test/test.py @@ -656,21 +656,587 @@ record 31: record 32: - +(array([ 0.30000001, 0.5448544 , 0.77480197, 0.97584349, 1.13573945, + 1.24475539, 1.2962544 , 1.28710103, 1.21785283, 1.09272552, + 0.91933674, 0.7082426 , 0.47229454, 0.22585714, -0.01606643, + -0.23874778, -0.42862982, -0.57415301, -0.6664573 , -0.69992352, + -0.67251408, -0.58589762, -0.44534767, -0.25942117, -0.03943586, + 0.20121357, 0.44787762, 0.68553883, 0.89972788, 1.0774051 , + 1.20775461, 1.28283918, 1.29808831, 1.25257373, 1.14906585, + 0.99386656, 0.79642528, 0.56876069, 0.32473388, 0.07920124, + -0.15288824, -0.35740662, -0.52190179, -0.63635898, -0.69381076, + -0.69075894, -0.62739003, -0.5075599 , -0.3385666 , -0.13069656, + 0.10339352, 0.34945396, 0.59250361, 0.81774551, 1.01146686, + 1.16187334, 1.25980926, 1.29931164, 1.27797604, 1.1971004 , + 1.06160903, 0.87975079, 0.66259789, 0.42336911, 0.17663053, + -0.06259823, -0.2797519 , -0.46160996, -0.59710097, -0.67797607, + -0.69931161, -0.65980917, -0.56187314, -0.41146588, -0.21774435, + 0.00749773, 0.25054744, 0.49660596, 0.7306987 , 0.93856692, + 1.10756063, 1.22738981, 1.29075909, 1.29381061, 1.23635852, + 1.1219027 , 0.95740634, 0.7528879 , 0.52079749, 0.2752648 , + 0.03123802, -0.19642642, -0.39386547, -0.54906607, -0.6525743 , + -0.69808841, -0.68283898, -0.60775399, -0.47740453, -0.29972947, + -0.08553842, 0.15212469, 0.39878684, 0.63943672, 0.85942155, + 1.04534864, 1.18589854, 1.2725141 , 1.29992342, 1.2664578 , + 1.17415261, 1.0286293 , 0.83874667, 0.61606491, 0.37414294, + 0.12770344, -0.1082412 , -0.31933719, -0.49272597, -0.61785328, + -0.6871013 , -0.69625437, -0.64475471, -0.53574032, -0.37584305, + -0.17479956, 0.05514668, 0.30000135], dtype=float32), + {'checksum': -25004, + 'note': '', + 'noteSize': 0, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 638}, + {'aModified': 0, + 'bname': array(['r', 'a', 'd', 'i', 'u', 's', 'D', 'a', 't', 'a', '', '', '', '', + '', '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 0.04908738521234052, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845545774, + 'next': 0, + 'npnts': 128, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': array([ 0.30000001, 0.5448544 , 0.77480197, 0.97584349]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}) record 33: - +(array([ 0. , 0.0494739 , 0.0989478 , 0.1484217 , 0.1978956 , + 0.24736951, 0.29684341, 0.34631732, 0.3957912 , 0.44526511, + 0.49473903, 0.54421294, 0.59368682, 0.6431607 , 0.69263464, + 0.74210852, 0.79158241, 0.84105635, 0.89053023, 0.94000411, + 0.98947805, 1.03895199, 1.08842587, 1.13789964, 1.18737364, + 1.23684752, 1.2863214 , 1.3357954 , 1.38526928, 1.43474305, + 1.48421705, 1.53369093, 1.58316481, 1.63263881, 1.68211269, + 1.73158658, 1.78106046, 1.83053434, 1.88000822, 1.92948222, + 1.9789561 , 2.02842999, 2.07790399, 2.12737775, 2.17685175, + 2.22632551, 2.27579927, 2.32527351, 2.37474728, 2.42422128, + 2.47369504, 2.52316904, 2.5726428 , 2.6221168 , 2.67159081, + 2.72106457, 2.77053857, 2.82001233, 2.86948609, 2.91896009, + 2.9684341 , 3.0179081 , 3.06738186, 3.11685586, 3.16632962, + 3.21580338, 3.26527762, 3.31475139, 3.36422539, 3.41369915, + 3.46317315, 3.51264691, 3.56212091, 3.61159492, 3.66106868, + 3.71054268, 3.76001644, 3.8094902 , 3.85896444, 3.90843821, + 3.95791221, 4.00738621, 4.05685997, 4.10633373, 4.15580797, + 4.20528126, 4.2547555 , 4.30422926, 4.3537035 , 4.40317726, + 4.45265102, 4.50212526, 4.55159855, 4.60107279, 4.65054703, + 4.70002079, 4.74949455, 4.79896832, 4.84844255, 4.89791584, + 4.94739008, 4.99686432, 5.04633808, 5.09581184, 5.14528561, + 5.19475985, 5.24423361, 5.29370737, 5.34318161, 5.3926549 , + 5.44212914, 5.4916029 , 5.54107714, 5.5905509 , 5.64002466, + 5.6894989 , 5.73897219, 5.78844643, 5.83792019, 5.88739443, + 5.93686819, 5.98634195, 6.03581619, 6.08528948, 6.13476372, + 6.18423796, 6.23371172, 6.28318548], dtype=float32), + {'checksum': 28621, + 'note': '', + 'noteSize': 0, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 638}, + {'aModified': 0, + 'bname': array(['a', 'n', 'g', 'l', 'e', 'D', 'a', 't', 'a', '', '', '', '', '', '', + '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 0.04908738521234052, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845470039, + 'next': 0, + 'npnts': 128, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': array([ 0. , 0.0494739, 0.0989478, 0.1484217]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}) record 34: - +(array([ 1.83690956e-17, 2.69450769e-02, 7.65399113e-02, + 1.44305170e-01, 2.23293692e-01, 3.04783821e-01, + 3.79158467e-01, 4.36888516e-01, 4.69528973e-01, + 4.70633775e-01, 4.36502904e-01, 3.66688997e-01, + 2.64211357e-01, 1.35452762e-01, -1.02594923e-02, + -1.61356136e-01, -3.04955602e-01, -4.27943677e-01, + -5.18107474e-01, -5.65230608e-01, -5.62046587e-01, + -5.04969478e-01, -3.94532531e-01, -2.35490710e-01, + -3.65724117e-02, 1.90097600e-01, 4.29877043e-01, + 6.66696191e-01, 8.84287775e-01, 1.06744885e+00, + 1.20323074e+00, 1.28195620e+00, 1.29798901e+00, + 1.25017929e+00, 1.14195395e+00, 9.81046736e-01, + 7.78884649e-01, 5.49682915e-01, 3.09332967e-01, + 7.41607845e-02, -1.40328899e-01, -3.20629656e-01, + -4.56221938e-01, -5.40310800e-01, -5.70244014e-01, + -5.47582209e-01, -4.77826297e-01, -3.69823217e-01, + -2.34920204e-01, -8.59207287e-02, 6.40354082e-02, + 2.02596441e-01, 3.19209903e-01, 4.05949473e-01, + 4.58081126e-01, 4.74326164e-01, 4.56804305e-01, + 4.10668582e-01, 3.43470216e-01, 2.64317334e-01, + 1.82909429e-01, 1.08534366e-01, 4.91267964e-02, + 1.04717268e-02, -4.36885841e-03, 4.64119762e-03, + 3.45129520e-02, 7.95329511e-02, 1.31838784e-01, + 1.82213545e-01, 2.21028924e-01, 2.39245579e-01, + 2.29380637e-01, 1.86348081e-01, 1.08093813e-01, + -4.03938442e-03, -1.45255283e-01, -3.07566285e-01, + -4.80366081e-01, -6.51240766e-01, -8.07001889e-01, + -9.34792042e-01, -1.02321768e+00, -1.06338477e+00, + -1.04975033e+00, -9.80714381e-01, -8.58889818e-01, + -6.91040277e-01, -4.87653464e-01, -2.62210011e-01, + -3.01902127e-02, 1.92100301e-01, 3.88785005e-01, + 5.45667768e-01, 6.51326835e-01, 6.98035002e-01, + 6.82368934e-01, 6.05477571e-01, 4.72992837e-01, + 2.94585884e-01, 8.31873119e-02, -1.46010652e-01, + -3.76755983e-01, -5.93006968e-01, -7.80143738e-01, + -9.26071882e-01, -1.02209401e+00, -1.06349015e+00, + -1.04976654e+00, -9.84551251e-01, -8.75151932e-01, + -7.31834948e-01, -5.66861272e-01, -3.93398553e-01, + -2.24383846e-01, -7.14399144e-02, 5.60413450e-02, + 1.51621893e-01, 2.12215677e-01, 2.38205954e-01, + 2.33226836e-01, 2.03656554e-01, 1.57870770e-01, + 1.05330117e-01, 5.55786416e-02, 1.72677450e-02, + -2.72719120e-03, 5.24539061e-08], dtype=float32), + {'checksum': 23021, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': 'PolarRadiusFunction(radiusData,1,0) * cos(PolarAngleFunction(angleData,3,1,2))\x00', + 'formulaSize': 80, + 'note': '', + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 832}, + {'aModified': 0, + 'bname': array(['W', '_', 'p', 'l', 'r', 'X', '5', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 24, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], + ['', '', '', ''], + ['', '', '', ''], + ['', '', '', '']], + dtype='|S1'), + 'fileName': 0, + 'formula': 8054500, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([128, 0, 0, 0]), + 'next': 8054516, + 'npnts': 128, + 'sIndices': 0, + 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 1.8369095638207904e-17, + 'wModified': 0, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}) record 35: - +(array([ 0.30000001, 0.54418772, 0.77101213, 0.96511477, 1.1135726 , + 1.20686483, 1.23956215, 1.21068466, 1.12370288, 0.98618096, + 0.80910152, 0.60592639, 0.39147732, 0.18073183, -0.01236418, + -0.17596789, -0.30120692, -0.38277394, -0.41920158, -0.41280419, + -0.36929506, -0.29712263, -0.20658807, -0.10882771, -0.01475283, + 0.06595302, 0.12569843, 0.15962352, 0.16596791, 0.14613269, + 0.10443594, 0.04758934, -0.01605497, -0.0774129 , -0.12764584, + -0.15911636, -0.16622847, -0.14607331, -0.09881912, -0.02780312, + 0.06068454, 0.15791172, 0.25346208, 0.33617997, 0.3952153 , + 0.42107204, 0.40657136, 0.34763175, 0.24380288, 0.09848462, + -0.08117689, -0.28473276, -0.49916485, -0.70986813, -0.90179092, + -1.06064332, -1.17407382, -1.23270524, -1.23095524, -1.16755545, + -1.04573321, -0.87303019, -0.66077417, -0.42323959, -0.1765765 , + 0.06242594, 0.2776148 , 0.45470679, 0.58236426, 0.65303123, + 0.66346282, 0.61490625, 0.51291907, 0.36684951, 0.18901938, + -0.00631659, -0.20414437, -0.389898 , -0.55060786, -0.67586488, + -0.75857663, -0.79539269, -0.78681922, -0.73699296, -0.65315133, + -0.54485315, -0.42300734, -0.29883695, -0.18282266, -0.08376524, + -0.00802278, 0.0409977 , 0.06305727, 0.06099379, 0.04033075, + 0.00863387, -0.02533132, -0.05255322, -0.06475239, -0.05528941, + -0.01991711, 0.04269439, 0.13071296, 0.23921135, 0.36052904, + 0.48491719, 0.60139763, 0.69877088, 0.76667541, 0.79660165, + 0.78277934, 0.72283876, 0.6181944 , 0.47410288, 0.29939076, + 0.10585135, -0.09260413, -0.28104633, -0.44468346, -0.57008827, + -0.64630753, -0.66580337, -0.62512833, -0.52528399, -0.37171093, + -0.17394456, 0.0550792 , 0.30000135], dtype=float32), + {'checksum': -9146, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': 'PolarRadiusFunction(radiusData,1,0) * sin(PolarAngleFunction(angleData,3,1,2))\x00', + 'formulaSize': 80, + 'note': 'shadowX=W_plrX5,appendRadius=radiusData,appendAngleData=angleData,angleDataUnits=2', + 'noteSize': 82, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 832}, + {'aModified': 0, + 'bname': array(['W', '_', 'p', 'l', 'r', 'Y', '5', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 26, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], + ['', '', '', ''], + ['', '', '', ''], + ['', '', '', '']], + dtype='|S1'), + 'fileName': 0, + 'formula': 8054532, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([128, 0, 0, 0]), + 'next': 8084972, + 'npnts': 128, + 'sIndices': 0, + 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 0.30000001192092896, + 'wModified': 0, + 'waveNoteH': 7996608, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}) record 36: - +(array([ 0.2617994 , 0.27842158, 0.29504377, 0.31166595, 0.32828814, + 0.34491032, 0.36153251, 0.3781547 , 0.39477688, 0.41139907, + 0.42802125, 0.44464344, 0.46126559, 0.47788778, 0.49450997, + 0.51113212, 0.52775431, 0.54437649, 0.56099868, 0.57762086, + 0.59424305, 0.61086524, 0.62748742, 0.64410961, 0.66073179, + 0.67735398, 0.69397616, 0.71059835, 0.72722054, 0.74384272, + 0.76046491, 0.77708709, 0.79370928, 0.81033146, 0.82695365, + 0.84357584, 0.86019802, 0.87682021, 0.89344239, 0.91006458, + 0.92668676, 0.94330889, 0.95993114, 0.97655326, 0.99317551, + 1.00979757, 1.02641988, 1.04304194, 1.05966425, 1.07628632, + 1.09290862, 1.10953069, 1.12615299, 1.14277506, 1.15939736, + 1.17601943, 1.19264174, 1.2092638 , 1.22588611, 1.24250817, + 1.25913048, 1.27575254, 1.29237485, 1.30899692], dtype=float32), + {'checksum': 14307, + 'note': '', + 'noteSize': 0, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 382}, + {'aModified': 0, + 'bname': array(['a', 'n', 'g', 'l', 'e', 'Q', '1', '', '', '', '', '', '', '', '', + '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845473705, + 'next': 0, + 'npnts': 64, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': array([ 0.2617994 , 0.27842158, 0.29504377, 0.31166595]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}) record 37: - +(array([ -8.34064484, -7.66960144, -6.62294245, -6.82878971, + -8.6383152 , -11.20019722, -13.83398628, -15.95139503, + -16.18096733, -13.58062267, -9.26843071, -5.34649038, + -3.01010084, -2.30953455, -2.73682952, -3.72112942, + -4.85171413, -5.63053226, -5.48626232, -4.49401283, + -3.53216696, -3.34821796, -4.07400894, -5.87675714, + -9.11268425, -12.98700237, -15.06296921, -13.71571922, + -10.23535728, -7.01303005, -5.23288727, -5.71091986, + -9.24852943, -14.06335735, -15.846241 , -12.78800964, + -7.8465519 , -4.56293297, -3.54999399, -3.67789125, + -4.10172844, -4.78980875, -6.20238352, -8.17891598, + -9.2803278 , -8.36780167, -6.3059268 , -4.85605574, + -4.54975414, -4.52917624, -3.99160147, -3.1971693 , + -2.93472862, -3.47230864, -4.7322526 , -6.80173016, + -9.08601665, -10.00928402, -8.87677383, -6.88120317, + -5.61007977, -5.6351161 , -6.41880989, -6.8738699 ], dtype=float32), + {'checksum': -12080, + 'note': '', + 'noteSize': 0, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 382}, + {'aModified': 0, + 'bname': array(['r', 'a', 'd', 'i', 'u', 's', 'Q', '1', '', '', '', '', '', '', '', + '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845473634, + 'next': 0, + 'npnts': 64, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': array([-8.34064484, -7.66960144, -6.62294245, -6.82878971]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}) record 38: - +(array([ 30.58058929, 31.08536911, 31.93481636, 31.57315445, + 29.68683434, 27.10366058, 24.47453499, 22.3495121 , + 21.98692894, 24.21500397, 27.95923996, 31.28394508, + 33.12408066, 33.46794128, 32.79909515, 31.64211464, + 30.36601639, 29.40137291, 29.22361755, 29.74564171, + 30.21624565, 30.02338219, 29.0822773 , 27.28613091, + 24.38687515, 21.04944038, 19.16931915, 19.92274094, + 22.23493385, 24.27418709, 25.1893177 , 24.44671249, + 21.56310272, 17.87704659, 16.35500908, 18.09041786, + 20.97328949, 22.66550255, 22.84443283, 22.29068756, + 21.55643272, 20.67234993, 19.38551521, 17.81604385, + 16.77393341, 16.8293457 , 17.4496479 , 17.6982975 , + 17.34101677, 16.83446693, 16.56042671, 16.38027191, + 15.94310474, 15.16159916, 14.10328865, 12.76812935, + 11.41363049, 10.60795975, 10.52314186, 10.67826462, + 10.5454855 , 9.99268055, 9.22939587, 8.5736742 ], dtype=float32), + {'checksum': -5745, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': 'PolarRadiusFunction(radiusQ1,1,-40) * cos(PolarAngleFunction(angleQ1,2,2,2))\x00', + 'formulaSize': 78, + 'note': '', + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 576}, + {'aModified': 0, + 'bname': array(['W', '_', 'p', 'l', 'r', 'X', '6', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 30, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], + ['', '', '', ''], + ['', '', '', ''], + ['', '', '', '']], + dtype='|S1'), + 'fileName': 0, + 'formula': 8052116, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([64, 0, 0, 0]), + 'next': 8324392, + 'npnts': 64, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 30.580589294433594, + 'wModified': 0, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}) record 39: - +(array([ 8.19404411, 8.88563347, 9.70543861, 10.17177773, + 10.11173058, 9.73756695, 9.25513077, 8.8788929 , + 9.16085339, 10.56489944, 12.75579453, 14.90572262, + 16.46352959, 17.33401871, 17.68511391, 17.74635315, + 17.70048141, 17.79942513, 18.36241531, 19.38741684, + 20.41767311, 21.02259827, 21.09260368, 20.4905529 , + 18.95538521, 16.9299469 , 15.94969368, 17.14490509, + 19.78741264, 22.33615875, 23.96352196, 24.04369545, + 21.92454147, 18.79150391, 17.77407646, 20.32803917, + 24.37140465, 27.24079132, 28.40307808, 28.67787933, + 28.70550728, 28.50283432, 27.68538666, 26.36607552, + 25.73583984, 26.78374672, 28.8236084 , 30.36226463, + 30.91939545, 31.22146797, 31.97431755, 32.95656204, + 33.4611969 , 33.23248672, 32.3250885 , 30.64473915, + 28.72983551, 28.05199242, 29.29024887, 31.3501091 , + 32.7331543 , 32.87995529, 32.28799438, 31.99738503], dtype=float32), + {'checksum': -16604, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': 'PolarRadiusFunction(radiusQ1,1,-40) * sin(PolarAngleFunction(angleQ1,2,2,2))\x00', + 'formulaSize': 78, + 'note': 'shadowX=W_plrX6,appendRadius=radiusQ1,appendAngleData=angleQ1,angleDataUnits=2', + 'noteSize': 78, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 576}, + {'aModified': 0, + 'bname': array(['W', '_', 'p', 'l', 'r', 'Y', '6', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], + dtype='|S1'), + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 32, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], + ['', '', '', ''], + ['', '', '', ''], + ['', '', '', '']], + dtype='|S1'), + 'fileName': 0, + 'formula': 7995612, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([64, 0, 0, 0]), + 'next': 0, + 'npnts': 64, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 8.19404411315918, + 'wModified': 0, + 'waveNoteH': 7998208, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}) record 40: record 41: @@ -701,6 +1267,7 @@ from igor.binarywave import load as loadibw from igor.packed import load as loadpxp +from igor.packed import WaveRecord _this_dir = os.path.dirname(__file__) @@ -720,7 +1287,10 @@ def dumppxp(filename, strict=True): records = loadpxp(path, strict=strict) for i,record in enumerate(records): print('record {}:'.format(i)) - pprint(record) + if isinstance(record, WaveRecord): + pprint(record.wave) + else: + pprint(record) def pprint(data): lines = pformat(data).splitlines() From f80fadab6d17098793f14f443d0a88aab648ebf8 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 17 Jul 2012 08:59:17 -0400 Subject: [PATCH 21/76] Move need_to_reorder_bytes from binarywave to util. We'll be needing it for packed.load, so it's not binarywave-specific. --- igor/binarywave.py | 12 ++---------- igor/util.py | 8 ++++++++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index 70af310..f9c4e18 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -36,6 +36,7 @@ from .struct import Field as _Field from .util import assert_null as _assert_null from .util import byte_order as _byte_order +from .util import need_to_reorder_bytes as _need_to_reorder_bytes from .util import checksum as _checksum @@ -226,15 +227,6 @@ # End IGOR constants and typedefs from IgorBin.h -# Begin functions from ReadWave.c - -def _need_to_reorder_bytes(version): - # If the low order byte of the version field of the BinHeader - # structure is zero then the file is from a platform that uses - # different byte-ordering and therefore all data will need to be - # reordered. - return version & 0xFF == 0 - def _version_structs(version, byte_order): if version == 1: bin = BinHeader1 @@ -271,7 +263,7 @@ def load(filename, strict=True): version = BinHeaderCommon.unpack_dict_from(b)['version'] needToReorderBytes = _need_to_reorder_bytes(version) byteOrder = _byte_order(needToReorderBytes) - + if needToReorderBytes: BinHeaderCommon.set_byte_order(byteOrder) version = BinHeaderCommon.unpack_dict_from(b)['version'] diff --git a/igor/util.py b/igor/util.py index 55c015c..c263cd1 100644 --- a/igor/util.py +++ b/igor/util.py @@ -63,6 +63,14 @@ def byte_order(needToReorderBytes): return '<' # little-endian return '>' # big-endian +# From ReadWave.c +def need_to_reorder_bytes(version): + # If the low order byte of the version field of the BinHeader + # structure is zero then the file is from a platform that uses + # different byte-ordering and therefore all data will need to be + # reordered. + return version & 0xFF == 0 + # From ReadWave.c def checksum(buffer, byte_order, oldcksum, numbytes): x = _numpy.ndarray( From e10e3e20945a9d3b91a057fcbbb82547f26d5d63 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 18 Jul 2012 16:34:05 -0400 Subject: [PATCH 22/76] Extend igor.struct.Structure and .Field to support nesting. --- igor/binarywave.py | 8 +- igor/packed.py | 2 +- igor/struct.py | 467 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 375 insertions(+), 102 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index f9c4e18..4fa5e4a 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -260,13 +260,13 @@ def load(filename, strict=True): try: BinHeaderCommon.set_byte_order('=') b = buffer(f.read(BinHeaderCommon.size)) - version = BinHeaderCommon.unpack_dict_from(b)['version'] + version = BinHeaderCommon.unpack_from(b)['version'] needToReorderBytes = _need_to_reorder_bytes(version) byteOrder = _byte_order(needToReorderBytes) if needToReorderBytes: BinHeaderCommon.set_byte_order(byteOrder) - version = BinHeaderCommon.unpack_dict_from(b)['version'] + version = BinHeaderCommon.unpack_from(b)['version'] bin_struct,wave_struct,checkSumSize = _version_structs( version, byteOrder) @@ -276,8 +276,8 @@ def load(filename, strict=True): raise ValueError( ('This does not appear to be a valid Igor binary wave file. ' 'Error in checksum: should be 0, is {}.').format(c)) - bin_info = bin_struct.unpack_dict_from(b) - wave_info = wave_struct.unpack_dict_from(b, offset=bin_struct.size) + bin_info = bin_struct.unpack_from(b) + wave_info = wave_struct.unpack_from(b, offset=bin_struct.size) if version in [1,2,3]: tail = 16 # 16 = size of wData field in WaveHeader2 structure waveDataSize = bin_info['wfmSize'] - wave_struct.size diff --git a/igor/packed.py b/igor/packed.py index ebcbdfc..69d7353 100644 --- a/igor/packed.py +++ b/igor/packed.py @@ -124,7 +124,7 @@ def load(filename, strict=True, ignore_unknown=True): b = buffer(f.read(PackedFileRecordHeader.size)) if not b: break - header = PackedFileRecordHeader.unpack_dict_from(b) + header = PackedFileRecordHeader.unpack_from(b) data = buffer(f.read(header['numDataBytes'])) record_type = RECORD_TYPE.get( header['recordType'] & PACKEDRECTYPE_MASK, UnknownRecord) diff --git a/igor/struct.py b/igor/struct.py index d16ca8a..32c2b93 100644 --- a/igor/struct.py +++ b/igor/struct.py @@ -1,6 +1,12 @@ # Copyright -"Structure and Field classes for declaring structures " +"""Structure and Field classes for declaring structures + +There are a few formats that can be used to represent the same data, a +binary packed format with all the data in a buffer, a linearized +format with each field in a single Python list, and a nested format +with each field in a hierarchy of Python dictionaries. +""" from __future__ import absolute_import import struct as _struct @@ -14,21 +20,249 @@ class Field (object): """Represent a Structure field. + The format argument can be a format character from the ``struct`` + documentation (e.g., ``c`` for ``char``, ``h`` for ``short``, ...) + or ``Structure`` instance (for building nested structures). + + Examples + -------- + + >>> from pprint import pprint + >>> import numpy + + Example of an unsigned short integer field: + + >>> time = Field( + ... 'I', 'time', default=0, help='POSIX time') + >>> time.total_count + 1 + >>> list(time.pack_data(1)) + [1] + >>> list(time.pack_item(2)) + [2] + >>> time.unpack_data([3]) + 3 + >>> time.unpack_item([4]) + 4 + + Example of a multi-dimensional float field: + + >>> data = Field( + ... 'f', 'data', help='example data', count=(2,3,4)) + >>> data.total_count + 24 + >>> list(data.indexes()) # doctest: +ELLIPSIS + [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 1, 0], ..., [1, 2, 3]] + >>> list(data.pack_data( + ... [[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], + ... [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]) + ... ) # doctest: +ELLIPSIS + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ..., 19, 20, 21, 22, 23] + >>> list(data.pack_item(3)) + [3] + >>> data.unpack_data(range(data.total_count)) + array([[[ 0, 1, 2, 3], + [ 4, 5, 6, 7], + [ 8, 9, 10, 11]], + + [[12, 13, 14, 15], + [16, 17, 18, 19], + [20, 21, 22, 23]]]) + >>> data.unpack_item([3]) + 3 + + Example of a nested structure field: + + >>> run = Structure('run', fields=[time, data]) + >>> runs = Field(run, 'runs', help='pair of runs', count=2) + >>> runs.total_count # = 2 * (1 + 24) + 50 + >>> data1 = numpy.arange(data.total_count).reshape(data.count) + >>> data2 = data1 + data.total_count + >>> list(runs.pack_data( + ... [{'time': 100, 'data': data1}, + ... {'time': 101, 'data': data2}]) + ... ) # doctest: +ELLIPSIS + [100, 0, 1, 2, ..., 22, 23, 101, 24, 25, ..., 46, 47] + >>> list(runs.pack_item({'time': 100, 'data': data1}) + ... ) # doctest: +ELLIPSIS + [100, 0, 1, 2, ..., 22, 23] + >>> pprint(runs.unpack_data(range(runs.total_count))) + [{'data': array([[[ 1, 2, 3, 4], + [ 5, 6, 7, 8], + [ 9, 10, 11, 12]], + + [[13, 14, 15, 16], + [17, 18, 19, 20], + [21, 22, 23, 24]]]), + 'time': 0}, + {'data': array([[[26, 27, 28, 29], + [30, 31, 32, 33], + [34, 35, 36, 37]], + + [[38, 39, 40, 41], + [42, 43, 44, 45], + [46, 47, 48, 49]]]), + 'time': 25}] + >>> pprint(runs.unpack_item(range(runs.structure_count))) + {'data': array([[[ 1, 2, 3, 4], + [ 5, 6, 7, 8], + [ 9, 10, 11, 12]], + + [[13, 14, 15, 16], + [17, 18, 19, 20], + [21, 22, 23, 24]]]), + 'time': 0} + + If you don't give enough values for an array field, the remaining + values are filled in with their defaults. + + >>> list(data.pack_data( + ... [[[0, 1, 2, 3], [4, 5, 6]], [[10]]])) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: no default for + >>> data.default = 0 + >>> list(data.pack_data( + ... [[[0, 1, 2, 3], [4, 5, 6]], [[10]]])) + [0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + See Also -------- Structure """ def __init__(self, format, name, default=None, help=None, count=1): - self.format = format # See the struct documentation + self.format = format self.name = name - self.default = None + self.default = default self.help = help self.count = count - self.total_count = _numpy.prod(count) + self.item_count = _numpy.prod(count) # number of item repeats + if isinstance(self.format, Structure): + self.structure_count = sum(f.total_count for f in format.fields) + self.total_count = self.item_count * self.structure_count + else: + self.total_count = self.item_count # struct.Struct format chars + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return '<{} {} {}>'.format( + self.__class__.__name__, self.name, id(self)) + + def indexes(self): + """Iterate through indexes to a possibly multi-dimensional array""" + assert self.item_count > 1, self + try: + i = [0] * len(self.count) + except TypeError: # non-iterable count + for i in range(self.count): + yield i + else: + for i in range(self.item_count): + index = [] + for j,c in enumerate(reversed(self.count)): + index.insert(0, i % c) + i /= c + yield index + + def pack_data(self, data=None): + """Linearize a single field's data to a flat list. + + If the field is repeated (count > 1), the incoming data should + be iterable with each iteration returning a single item. + """ + if self.item_count > 1: + if data is None: + data = [] + if hasattr(data, 'flat'): # take advantage of numpy's ndarray.flat + items = 0 + for item in data.flat: + items += 1 + for arg in self.pack_item(item): + yield arg + if items < self.item_count: + if f.default is None: + raise ValueError( + 'no default for {}.{}'.format(self, f)) + for i in range(self.item_count - items): + yield f.default + else: + for index in self.indexes(): + try: + if isinstance(index, int): + item = data[index] + else: + item = data + for i in index: + item = item[i] + except IndexError: + item = None + for arg in self.pack_item(item): + yield arg + else: + for arg in self.pack_item(data): + yield arg + + def pack_item(self, item=None): + """Linearize a single count of the field's data to a flat iterable + """ + if isinstance(self.format, Structure): + for i in self.format._pack_item(item): + yield i + elif item is None: + if self.default is None: + raise ValueError('no default for {}'.format(self)) + yield self.default + else: + yield item + + def unpack_data(self, data): + """Inverse of .pack_data""" + iterator = iter(data) + try: + items = [iterator.next() for i in range(self.total_count)] + except StopIteration: + raise ValueError('not enough data to unpack {}'.format(self)) + try: + iterator.next() + except StopIteration: + pass + else: + raise ValueError('too much data to unpack {}'.format(self)) + if isinstance(self.format, Structure): + # break into per-structure clumps + s = self.structure_count + items = zip(*[items[i::s] for i in range(s)]) + else: + items = [[i] for i in items] + unpacked = [self.unpack_item(i) for i in items] + if self.count == 1: + return unpacked[0] + if isinstance(self.format, Structure): + try: + len(self.count) + except TypeError: + pass + else: + raise NotImplementedError('reshape Structure field') + else: + unpacked = _numpy.array(unpacked) + unpacked = unpacked.reshape(self.count) + return unpacked + + def unpack_item(self, item): + """Inverse of .unpack_item""" + if isinstance(self.format, Structure): + return self.format._unpack_item(item) + else: + assert len(item) == 1, item + return item[0] class Structure (_struct.Struct): - """Represent a C structure. + r"""Represent a C structure. A convenient wrapper around struct.Struct that uses Fields and adds dict-handling methods for transparent name assignment. @@ -40,41 +274,86 @@ class Structure (_struct.Struct): Examples -------- - Represent the C structure:: + >>> import array + >>> from pprint import pprint + + Represent the C structures:: + + struct run { + unsigned int time; + short data[2][3]; + } - struct thing { - short version; - long size[3]; + struct experiment { + unsigned short version; + struct run runs[2]; } As - >>> import array - >>> from pprint import pprint - >>> thing = Structure(name='thing', - ... fields=[Field('h', 'version'), Field('l', 'size', count=3)]) - >>> thing.set_byte_order('>') - >>> b = array.array('b', range(2+4*3)) - >>> d = thing.unpack_dict_from(buffer=b) + >>> time = Field('I', 'time', default=0, help='POSIX time') + >>> data = Field( + ... 'h', 'data', default=0, help='example data', count=(2,3)) + >>> run = Structure('run', fields=[time, data]) + >>> version = Field( + ... 'H', 'version', default=1, help='example version') + >>> runs = Field(run, 'runs', help='pair of runs', count=2) + >>> experiment = Structure('experiment', fields=[version, runs]) + + The structures automatically calculate the flattened data format: + + >>> run.format + '=Ihhhhhh' + >>> run.size # 4 + 2*3*2 + 16 + >>> experiment.format + '=HIhhhhhhIhhhhhh' + >>> experiment.size # 2 + 2*(4 + 2*3*2) + 34 + + You can read data out of any object supporting the buffer + interface: + + >>> b = array.array('B', range(experiment.size)) + >>> experiment.set_byte_order('>') + >>> d = experiment.unpack_from(buffer=b) >>> pprint(d) - {'size': array([ 33752069, 101124105, 168496141]), 'version': 1} - >>> [hex(x) for x in d['size']] - ['0x2030405L', '0x6070809L', '0xa0b0c0dL'] - - You can even get fancy with multi-dimensional arrays. - - >>> thing = Structure(name='thing', - ... fields=[Field('h', 'version'), Field('l', 'size', count=(3,2))]) - >>> thing.set_byte_order('>') - >>> b = array.array('b', range(2+4*3*2)) - >>> d = thing.unpack_dict_from(buffer=b) - >>> d['size'].shape - (3, 2) + {'runs': [{'data': array([[1543, 2057, 2571], + [3085, 3599, 4113]]), + 'time': 33752069}, + {'data': array([[5655, 6169, 6683], + [7197, 7711, 8225]]), + 'time': 303240213}], + 'version': 1} + >>> [hex(x) for x in d['runs'][0]['data'].flat] + ['0x607L', '0x809L', '0xa0bL', '0xc0dL', '0xe0fL', '0x1011L'] + + You can also read out from strings: + + >>> d = experiment.unpack(b.tostring()) >>> pprint(d) - {'size': array([[ 33752069, 101124105], - [168496141, 235868177], - [303240213, 370612249]]), + {'runs': [{'data': array([[1543, 2057, 2571], + [3085, 3599, 4113]]), + 'time': 33752069}, + {'data': array([[5655, 6169, 6683], + [7197, 7711, 8225]]), + 'time': 303240213}], 'version': 1} + + If you don't give enough values for an array field, the remaining + values are filled in with their defaults. + + >>> experiment.pack_into(buffer=b, data=d) + >>> b.tostring()[:17] + '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10' + >>> b.tostring()[17:] + '\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !' + >>> run0 = d['runs'].pop(0) + >>> b = experiment.pack(data=d) + >>> b[:17] + '\x00\x01\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f ' + >>> b[17:] + '!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' """ def __init__(self, name, fields, byte_order='='): # '=' for native byte order, standard size and alignment @@ -86,6 +365,10 @@ def __init__(self, name, fields, byte_order='='): def __str__(self): return self.name + def __repr__(self): + return '<{} {} {}>'.format( + self.__class__.__name__, self.name, id(self)) + def set_byte_order(self, byte_order): """Allow changing the format byte_order on the fly. """ @@ -94,67 +377,67 @@ def set_byte_order(self, byte_order): return # no need to change anything format = [] for field in self.fields: - format.extend([field.format]*field.total_count) + if isinstance(field.format, Structure): + field_format = field.format.sub_format( + ) * field.item_count + else: + field_format = [field.format]*field.item_count + format.extend(field_format) super(Structure, self).__init__( format=byte_order+''.join(format).replace('P', 'L')) - def _flatten_args(self, args): - # handle Field.count > 0 - flat_args = [] - for a,f in zip(args, self.fields): - if f.total_count > 1: - flat_args.extend(a) - else: - flat_args.append(a) - return flat_args + def sub_format(self): + return self.format.lstrip('=<>') # byte order handled by parent - def _unflatten_args(self, args): - # handle Field.count > 0 - unflat_args = [] - i = 0 + def _pack_item(self, item=None): + """Linearize a single count of the structure's data to a flat iterable + """ + if item is None: + item = {} for f in self.fields: - if f.total_count > 1: - data = _numpy.array(args[i:i+f.total_count]) - data = data.reshape(f.count) - unflat_args.append(data) - else: - unflat_args.append(args[i]) - i += f.total_count - return unflat_args - - def pack(self, *args): - return super(Structure, self)(*self._flatten_args(args)) - - def pack_into(self, buffer, offset, *args): - return super(Structure, self).pack_into( - buffer, offset, *self._flatten_args(args)) + try: + data = item[f.name] + except KeyError: + data = None + for arg in f.pack_data(data): + yield arg - def _clean_dict(self, dict): + def _unpack_item(self, args): + """Inverse of ._unpack_item""" + data = {} + iterator = iter(args) for f in self.fields: - if f.name not in dict: - if f.default != None: - dict[f.name] = f.default - else: - raise ValueError('{} field not set for {}'.format( - f.name, self.__class__.__name__)) - return dict - - def pack_dict(self, dict): - dict = self._clean_dict(dict) - return self.pack(*[dict[f.name] for f in self.fields]) - - def pack_dict_into(self, buffer, offset, dict={}): - dict = self._clean_dict(dict) - return self.pack_into(buffer, offset, - *[dict[f.name] for f in self.fields]) - - def unpack(self, string): - return self._unflatten_args( - super(Structure, self).unpack(string)) - - def unpack_from(self, buffer, offset=0): + try: + items = [iterator.next() for i in range(f.total_count)] + except StopIteration: + raise ValueError('not enough data to unpack {}.{}'.format( + self, f)) + data[f.name] = f.unpack_data(items) try: - args = super(Structure, self).unpack_from(buffer, offset) + iterator.next() + except StopIteration: + pass + else: + raise ValueError('too much data to unpack {}'.format(self)) + return data + + def pack(self, data): + args = list(self._pack_item(data)) + return super(Structure, self).pack(*args) + + def pack_into(self, buffer, offset=0, data={}): + args = list(self._pack_item(data)) + return super(Structure, self).pack_into( + buffer, offset, *args) + + def unpack(self, *args, **kwargs): + args = super(Structure, self).unpack(*args, **kwargs) + return self._unpack_item(args) + + def unpack_from(self, buffer, offset=0, *args, **kwargs): + try: + args = super(Structure, self).unpack_from( + buffer, offset, *args, **kwargs) except _struct.error as e: if not self.name in ('WaveHeader2', 'WaveHeader5'): raise @@ -166,16 +449,6 @@ def unpack_from(self, buffer, offset=0): # missing wData? Pad with zeros buffer += _buffer('\x00'*(self.size + offset - len(buffer))) args = super(Structure, self).unpack_from(buffer, offset) - unpacked = self._unflatten_args(args) - data = dict(zip([f.name for f in self.fields], - unpacked)) + data = self._unpack_item(args) assert data['npnts'] == 0, data['npnts'] - return self._unflatten_args(args) - - def unpack_dict(self, string): - return dict(zip([f.name for f in self.fields], - self.unpack(string))) - - def unpack_dict_from(self, buffer, offset=0): - return dict(zip([f.name for f in self.fields], - self.unpack_from(buffer, offset))) + return self._unpack_item(args) From cf045181403b434ada6837e4d964f9b3b0f0093f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 18 Jul 2012 18:20:52 -0400 Subject: [PATCH 23/76] struct: Add support for zero-count Fields. --- igor/struct.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/igor/struct.py b/igor/struct.py index 32c2b93..c9886a0 100644 --- a/igor/struct.py +++ b/igor/struct.py @@ -201,7 +201,7 @@ def pack_data(self, data=None): item = None for arg in self.pack_item(item): yield arg - else: + elif self.item_count: for arg in self.pack_item(data): yield arg @@ -354,6 +354,26 @@ class Structure (_struct.Struct): '\x00\x01\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f ' >>> b[17:] '!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + + If you set ``count=0``, the field is ignored. + + >>> experiment2 = Structure('experiment', fields=[ + ... version, Field('f', 'ignored', count=0), runs], byte_order='>') + >>> experiment2.format + '>HIhhhhhhIhhhhhh' + >>> d = experiment2.unpack(b) + >>> pprint(d) + {'ignored': array([], dtype=float64), + 'runs': [{'data': array([[5655, 6169, 6683], + [7197, 7711, 8225]]), + 'time': 303240213}, + {'data': array([[0, 0, 0], + [0, 0, 0]]), 'time': 0}], + 'version': 1} + >>> del d['ignored'] + >>> b2 = experiment2.pack(d) + >>> b2 == b + True """ def __init__(self, name, fields, byte_order='='): # '=' for native byte order, standard size and alignment @@ -397,6 +417,8 @@ def _pack_item(self, item=None): for f in self.fields: try: data = item[f.name] + except TypeError: + raise ValueError((f.name, item)) except KeyError: data = None for arg in f.pack_data(data): From d400cacfa6910a3b83414e4d8ad99e3b9b1ec465 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 Jul 2012 08:14:04 -0400 Subject: [PATCH 24/76] Split record handling into modules and implement VariablesRecord. --- igor/packed.py | 109 ++++---------------- igor/record/__init__.py | 30 ++++++ igor/record/base.py | 24 +++++ igor/record/folder.py | 11 ++ igor/record/gethistory.py | 7 ++ igor/record/history.py | 7 ++ igor/record/packedfile.py | 7 ++ igor/record/procedure.py | 7 ++ igor/record/recreation.py | 7 ++ igor/record/variables.py | 210 ++++++++++++++++++++++++++++++++++++++ igor/record/wave.py | 15 +++ setup.py | 1 + test/test.py | 80 ++++++++++++++- 13 files changed, 420 insertions(+), 95 deletions(-) create mode 100644 igor/record/__init__.py create mode 100644 igor/record/base.py create mode 100644 igor/record/folder.py create mode 100644 igor/record/gethistory.py create mode 100644 igor/record/history.py create mode 100644 igor/record/packedfile.py create mode 100644 igor/record/procedure.py create mode 100644 igor/record/recreation.py create mode 100644 igor/record/variables.py create mode 100644 igor/record/wave.py diff --git a/igor/packed.py b/igor/packed.py index 69d7353..48f933e 100644 --- a/igor/packed.py +++ b/igor/packed.py @@ -1,94 +1,15 @@ # Copyright -from io import BytesIO as _BytesIO +"Read IGOR Packed Experiment files files into records." -from .binarywave import load as _loadibw from .struct import Structure as _Structure from .struct import Field as _Field +from .util import byte_order as _byte_order +from .util import need_to_reorder_bytes as _need_to_reorder_bytes +from .record import RECORD_TYPE as _RECORD_TYPE +from .record.base import UnknownRecord as _UnknownRecord +from .record.base import UnusedRecord as _UnusedRecord -"Read IGOR Packed Experiment files files into records." - - -class Record (object): - def __init__(self, header, data): - self.header = header - self.data = data - - def __str__(self): - return self.__repr__() - - def __repr__(self): - return '<{} {}>'.format(self.__class__.__name__, id(self)) - - -class UnknownRecord (Record): - def __repr__(self): - return '<{}-{} {}>'.format( - self.__class__.__name__, self.header['recordType'], id(self)) - - -class UnusedRecord (Record): - pass - - -class VariablesRecord (Record): - pass - - -class HistoryRecord (Record): - pass - - -class WaveRecord (Record): - def __init__(self, *args, **kwargs): - super(WaveRecord, self).__init__(*args, **kwargs) - self.wave = _loadibw(_BytesIO(bytes(self.data)), strict=False) - - def __str__(self): - return str(self.wave) - - def __repr__(self): - return str(self.wave) - - -class RecreationRecord (Record): - pass - - -class ProcedureRecord (Record): - pass - - -class GetHistoryRecord (Record): - pass - - -class PackedFileRecord (Record): - pass - - -class FolderStartRecord (Record): - pass - - -class FolderEndRecord (Record): - pass - - -# From PackedFile.h -RECORD_TYPE = { - 0: UnusedRecord, - 1: VariablesRecord, - 2: HistoryRecord, - 3: WaveRecord, - 4: RecreationRecord, - 5: ProcedureRecord, - 6: UnusedRecord, - 7: GetHistoryRecord, - 8: PackedFileRecord, - 9: FolderStartRecord, - 10: FolderEndRecord, - } # Igor writes other kinds of records in a packed experiment file, for # storing things like pictures, page setup records, and miscellaneous @@ -118,21 +39,29 @@ def load(filename, strict=True, ignore_unknown=True): f = filename # filename is actually a stream object else: f = open(filename, 'rb') + byte_order = None + initial_byte_order = '=' try: while True: - PackedFileRecordHeader.set_byte_order('=') b = buffer(f.read(PackedFileRecordHeader.size)) if not b: break + PackedFileRecordHeader.set_byte_order(initial_byte_order) header = PackedFileRecordHeader.unpack_from(b) + if header['version'] and not byte_order: + need_to_reorder = _need_to_reorder_bytes(header['version']) + byte_order = initial_byte_order = _byte_order(need_to_reorder) + if need_to_reorder: + PackedFileRecordHeader.set_byte_order(byte_order) + header = PackedFileRecordHeader.unpack_from(b) data = buffer(f.read(header['numDataBytes'])) - record_type = RECORD_TYPE.get( - header['recordType'] & PACKEDRECTYPE_MASK, UnknownRecord) - if record_type in [UnknownRecord, UnusedRecord + record_type = _RECORD_TYPE.get( + header['recordType'] & PACKEDRECTYPE_MASK, _UnknownRecord) + if record_type in [_UnknownRecord, _UnusedRecord ] and not ignore_unknown: raise KeyError('unkown record type {}'.format( header['recordType'])) - records.append(record_type(header, data)) + records.append(record_type(header, data, byte_order=byte_order)) finally: if not hasattr(filename, 'read'): f.close() diff --git a/igor/record/__init__.py b/igor/record/__init__.py new file mode 100644 index 0000000..ffa6456 --- /dev/null +++ b/igor/record/__init__.py @@ -0,0 +1,30 @@ +# Copyright + +"Record parsers for IGOR's packed experiment files." + + +from .base import Record, UnknownRecord, UnusedRecord +from .variables import VariablesRecord +from .history import HistoryRecord +from .wave import WaveRecord +from .recreation import RecreationRecord +from .procedure import ProcedureRecord +from .gethistory import GetHistoryRecord +from .packedfile import PackedFileRecord +from .folder import FolderStartRecord, FolderEndRecord + + +# From PackedFile.h +RECORD_TYPE = { + 0: UnusedRecord, + 1: VariablesRecord, + 2: HistoryRecord, + 3: WaveRecord, + 4: RecreationRecord, + 5: ProcedureRecord, + 6: UnusedRecord, + 7: GetHistoryRecord, + 8: PackedFileRecord, + 9: FolderStartRecord, + 10: FolderEndRecord, + } diff --git a/igor/record/base.py b/igor/record/base.py new file mode 100644 index 0000000..a6990f8 --- /dev/null +++ b/igor/record/base.py @@ -0,0 +1,24 @@ +# Copyright + + +class Record (object): + def __init__(self, header, data, byte_order=None): + self.header = header + self.data = data + self.byte_order = byte_order + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return '<{} {}>'.format(self.__class__.__name__, id(self)) + + +class UnknownRecord (Record): + def __repr__(self): + return '<{}-{} {}>'.format( + self.__class__.__name__, self.header['recordType'], id(self)) + + +class UnusedRecord (Record): + pass diff --git a/igor/record/folder.py b/igor/record/folder.py new file mode 100644 index 0000000..b03e283 --- /dev/null +++ b/igor/record/folder.py @@ -0,0 +1,11 @@ +# Copyright + +from .base import Record + + +class FolderStartRecord (Record): + pass + + +class FolderEndRecord (Record): + pass diff --git a/igor/record/gethistory.py b/igor/record/gethistory.py new file mode 100644 index 0000000..d2e5c20 --- /dev/null +++ b/igor/record/gethistory.py @@ -0,0 +1,7 @@ +# Copyright + +from .base import Record + + +class GetHistoryRecord (Record): + pass diff --git a/igor/record/history.py b/igor/record/history.py new file mode 100644 index 0000000..0974cac --- /dev/null +++ b/igor/record/history.py @@ -0,0 +1,7 @@ +# Copyright + +from .base import Record + + +class HistoryRecord (Record): + pass diff --git a/igor/record/packedfile.py b/igor/record/packedfile.py new file mode 100644 index 0000000..9e12437 --- /dev/null +++ b/igor/record/packedfile.py @@ -0,0 +1,7 @@ +# Copyright + +from .base import Record + + +class PackedFileRecord (Record): + pass diff --git a/igor/record/procedure.py b/igor/record/procedure.py new file mode 100644 index 0000000..3c1e764 --- /dev/null +++ b/igor/record/procedure.py @@ -0,0 +1,7 @@ +# Copyright + +from .base import Record + + +class ProcedureRecord (Record): + pass diff --git a/igor/record/recreation.py b/igor/record/recreation.py new file mode 100644 index 0000000..3bc9cb4 --- /dev/null +++ b/igor/record/recreation.py @@ -0,0 +1,7 @@ +# Copyright + +from .base import Record + + +class RecreationRecord (Record): + pass diff --git a/igor/record/variables.py b/igor/record/variables.py new file mode 100644 index 0000000..e55d800 --- /dev/null +++ b/igor/record/variables.py @@ -0,0 +1,210 @@ +# Copyright + +from ..binarywave import TYPE_TABLE as _TYPE_TABLE +from ..struct import Structure as _Structure +from ..struct import Field as _Field +from ..util import byte_order as _byte_order +from ..util import need_to_reorder_bytes as _need_to_reorder_bytes +from .base import Record + + +VarHeaderCommon = _Structure( + name='VarHeaderCommon', + fields=[ + _Field('h', 'version', help='Version number for this header.'), + ]) + +# From Variables.h +VarHeader1 = _Structure( + name='VarHeader1', + fields=[ + _Field('h', 'version', help='Version number is 1 for this header.'), + _Field('h', 'numSysVars', help='Number of system variables (K0, K1, ...).'), + _Field('h', 'numUserVars', help='Number of user numeric variables -- may be zero.'), + _Field('h', 'numUserStrs', help='Number of user string variables -- may be zero.'), + ]) + +# From Variables.h +VarHeader2 = _Structure( + name='VarHeader2', + fields=[ + _Field('h', 'version', help='Version number is 2 for this header.'), + _Field('h', 'numSysVars', help='Number of system variables (K0, K1, ...).'), + _Field('h', 'numUserVars', help='Number of user numeric variables -- may be zero.'), + _Field('h', 'numUserStrs', help='Number of user string variables -- may be zero.'), + _Field('h', 'numDependentVars', help='Number of dependent numeric variables -- may be zero.'), + _Field('h', 'numDependentStrs', help='Number of dependent string variables -- may be zero.'), + ]) + +# From Variables.h +UserStrVarRec1 = _Structure( + name='UserStrVarRec1', + fields=[ + _Field('c', 'name', help='Name of the string variable.', count=32), + _Field('h', 'strLen', help='The real size of the following array.'), + _Field('c', 'data'), + ]) + +# From Variables.h +UserStrVarRec2 = _Structure( + name='UserStrVarRec2', + fields=[ + _Field('c', 'name', help='Name of the string variable.', count=32), + _Field('l', 'strLen', help='The real size of the following array.'), + _Field('c', 'data'), + ]) + +# From Variables.h +VarNumRec = _Structure( + name='VarNumRec', + fields=[ + _Field('h', 'numType', help='Type from binarywave.TYPE_TABLE'), + _Field('d', 'realPart', help='The real part of the number.'), + _Field('d', 'imagPart', help='The imag part if the number is complex.'), + _Field('l', 'reserved', help='Reserved - set to zero.'), + ]) + +# From Variables.h +UserNumVarRec = _Structure( + name='UserNumVarRec', + fields=[ + _Field('c', 'name', help='Name of the string variable.', count=32), + _Field('h', 'type', help='0 = string, 1 = numeric.'), + _Field(VarNumRec, 'num', help='Type and value of the variable if it is numeric. Not used for string.'), + ]) + +# From Variables.h +UserDependentVarRec = _Structure( + name='UserDependentVarRec', + fields=[ + _Field('c', 'name', help='Name of the string variable.', count=32), + _Field('h', 'type', help='0 = string, 1 = numeric.'), + _Field(VarNumRec, 'num', help='Type and value of the variable if it is numeric. Not used for string.'), + _Field('h', 'formulaLen', help='The length of the dependency formula.'), + _Field('c', 'formula', help='Start of the dependency formula. A C string including null terminator.'), + ]) + + +class VariablesRecord (Record): + def __init__(self, *args, **kwargs): + super(VariablesRecord, self).__init__(*args, **kwargs) + # self.header['version'] # record version always 0? + version = self._set_byte_order_and_get_version() + self.structure = self._get_structure(version) + self.variables = self.structure.unpack_from(self.data) + self.variables.update(self._unpack_variable_length_structures(version)) + self._normalize_variables() + + def _set_byte_order_and_get_version(self): + if self.byte_order: + VarHeaderCommon.set_byte_order(self.byte_order) + else: + VarHeaderCommon.set_byte_order('=') + version = VarHeaderCommon.unpack_from(self.data)['version'] + if not self.byte_order: + need_to_reorder = _need_to_reorder_bytes(version) + self.byte_order = _byte_order(need_to_reorder) + if need_to_reorder: + VarHeaderCommon.set_byte_order(self.byte_order) + version = VarHeaderCommon.unpack_from(self.data)['version'] + return version + + def _get_structure(self, version): + if version == 1: + header_struct = VarHeader1 + elif version == 2: + header_struct = VarHeader2 + else: + raise NotImplementedError( + 'Variables record version {}'.format(version)) + header = header_struct.unpack_from(self.data) + fields = [ + _Field(header_struct, 'header', help='VarHeader'), + _Field('f', 'sysVars', help='system variables', + count=header['numSysVars']), + _Field(UserNumVarRec, 'userVars', help='user variables', + count=header['numUserVars']), + ] + return _Structure(name='variables', fields=fields) + + def _unpack_variable_length_structures(self, version): + data = {'userStrs': []} + offset = self.structure.size + + if version == 1: + user_str_var_struct = UserStrVarRec1 + elif version == 2: + user_str_var_struct = UserStrVarRec2 + else: + raise NotImplementedError( + 'Variables record version {}'.format(version)) + user_str_var_struct.set_byte_order(self.byte_order) + for i in range(self.variables['header']['numUserStrs']): + d = user_str_var_struct.unpack_from(self.data, offset) + offset += user_str_var_struct.size + end = offset + d['strLen'] - 1 # one character already in struct + if d['strLen']: + d['data'] = d['data'] + self.data[offset:end] + else: + d['data'] = '' + offset = end + data['userStrs'].append(d) + + if version == 2: + data.update({'dependentVars': [], 'dependentStrs': []}) + UserDependentVarRec.set_byte_order(self.byte_order) + for i in range(self.variables['header']['numDependentVars']): + d,offset = self._unpack_dependent_variable(offset) + data['dependentVars'].append(d) + for i in range(self.variables['header']['numDependentStrs']): + d,offset = self._unpack_dependent_variable(offset) + data['dependentStrs'].append(d) + + if offset != len(self.data): + raise ValueError('too much data ({} extra bytes)'.format( + len(self.data)-offset)) + return data + + def _unpack_dependent_variable(self, offset): + d = UserDependentVarRec.unpack_from(self.data, offset) + offset += UserDependentVarRec.size + end = offset + d['formulaLen'] - 1 # one character already in struct + if d['formulaLen']: + d['formula'] = d['formula'] + self.data[offset:end] + else: + d['formula'] = '' + offset = end + return (d, offset) + + def _normalize_variables(self): + user_vars = {} + for num_var in self.variables['userVars']: + key,value = self._normalize_user_numeric_variable(num_var) + user_vars[key] = value + self.variables['userVars'] = user_vars + user_strs = {} + for str_var in self.variables['userStrs']: + name = self._normalize_null_terminated_string(str_var['name']) + user_strs[name] = str_var['data'] + if self.variables['header']['version'] == 2: + raise NotImplementedError('normalize dependent variables') + self.variables['userStrs'] = user_strs + + def _normalize_null_terminated_string(self, string): + return string.tostring().split('\x00', 1)[0] + + def _normalize_user_numeric_variable(self, user_num_var): + user_num_var['name'] = self._normalize_null_terminated_string( + user_num_var['name']) + if user_num_var['type']: # numeric + value = self._normalize_numeric_variable(user_num_var['num']) + else: # string + value = None + return (user_num_var['name'], value) + + def _normalize_numeric_variable(self, num_var): + t = _TYPE_TABLE[num_var['numType']] + if num_var['numType'] % 2: # complex number + return t(complex(num_var['realPart'], num_var['imagPart'])) + else: + return t(num_var['realPart']) diff --git a/igor/record/wave.py b/igor/record/wave.py new file mode 100644 index 0000000..53d3af1 --- /dev/null +++ b/igor/record/wave.py @@ -0,0 +1,15 @@ +# Copyright + +from io import BytesIO as _BytesIO + +from ..binarywave import load as _loadibw +from . import Record + + +class WaveRecord (Record): + def __init__(self, *args, **kwargs): + super(WaveRecord, self).__init__(*args, **kwargs) + self.wave = _loadibw(_BytesIO(bytes(self.data)), strict=False) + + def __str__(self): + return str(self.wave) diff --git a/setup.py b/setup.py index c63b48b..446771e 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ ], packages=[ 'igor', + 'igor.record', ], scripts=[ 'bin/igorbinarywave.py', diff --git a/test/test.py b/test/test.py index 2c8c60e..3fd208f 100644 --- a/test/test.py +++ b/test/test.py @@ -1,3 +1,5 @@ +# Copyright + r"""Test the igor module by loading sample files. >>> dumpibw('mac-double.ibw', strict=False) # doctest: +REPORT_UDIFF @@ -652,7 +654,15 @@ record 29: record 30: - +{'header': {'numSysVars': 21, + 'numUserStrs': 0, + 'numUserVars': 0, + 'version': 1}, + 'sysVars': array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 128.]), + 'userStrs': {}, + 'userVars': {}} record 31: record 32: @@ -1242,13 +1252,70 @@ record 41: record 42: - +{'header': {'numSysVars': 21, + 'numUserStrs': 6, + 'numUserVars': 0, + 'version': 1}, + 'sysVars': array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 128.]), + 'userStrs': {'u_dataBase': ';PolarGraph0:,...,useCircles=2,maxArcLine=6;', + 'u_dbBadStringChars': ',;=:', + 'u_dbCurrBag': 'PolarGraph1', + 'u_dbCurrContents': ',appendRadius=radiusQ1,...,useCircles=2,maxArcLine=6;', + 'u_dbReplaceBadChars': '\xa9\xae\x99\x9f', + 'u_str': '2'}, + 'userVars': {}} record 43: record 44: record 45: - +{'header': {'numSysVars': 21, + 'numUserStrs': 10, + 'numUserVars': 28, + 'version': 1}, + 'sysVars': array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 128.]), + 'userStrs': {'u_colorList': 'black;blue;green;cyan;red;magenta;yellow;white;special', + 'u_debugStr': 'Turn Debugging On', + 'u_polAngleAxesWherePop': 'Off;Radius Start;Radius End;Radius Start and End;All Major Radii;At Listed Radii', + 'u_polAngleUnitsPop': 'deg;rad', + 'u_polLineStylePop': 'solid;dash 1;dash 2;dash 3;dash 4;dash 5;dash 6;dash 7;dash 8;dash 9;dash 10;dash 11;dash 12;dash 13;dash 14;dash 15;dash 16;dash 17;', + 'u_polOffOn': 'Off;On', + 'u_polRadAxesWherePop': ' Off; Angle Start; Angle Middle; Angle End; Angle Start and End; 0; 90; 180; -90; 0, 90; 90, 180; -180, -90; -90, 0; 0, 180; 90, -90; 0, 90, 180, -90; All Major Angles; At Listed Angles', + 'u_polRotPop': ' -90; 0; +90; +180', + 'u_popup': '', + 'u_prompt': ''}, + 'userVars': {'V_bottom': 232.0, + 'V_left': 1.0, + 'V_max': 2.4158518093414401, + 'V_min': -2.1848498883412, + 'V_right': 232.0, + 'V_top': 1.0, + 'u_UniqWaveNdx': 8.0, + 'u_UniqWinNdx': 3.0, + 'u_angle0': 0.0, + 'u_angleRange': 6.2831853071795862, + 'u_debug': 0.0, + 'u_majorDelta': 0.0, + 'u_numPlaces': 0.0, + 'u_polAngle0': 0.26179938779914941, + 'u_polAngleRange': 1.0471975511965976, + 'u_polInnerRadius': -20.0, + 'u_polMajorAngleInc': 0.26179938779914941, + 'u_polMajorRadiusInc': 10.0, + 'u_polMinorAngleTicks': 3.0, + 'u_polMinorRadiusTicks': 1.0, + 'u_polOuterRadius': 0.0, + 'u_segsPerMinorArc': 3.0, + 'u_tickDelta': 0.0, + 'u_var': 0.0, + 'u_x1': 11.450159535018935, + 'u_x2': 12.079591517721363, + 'u_y1': 42.732577139459856, + 'u_y2': 45.081649278814126}} record 46: record 47: @@ -1267,7 +1334,8 @@ from igor.binarywave import load as loadibw from igor.packed import load as loadpxp -from igor.packed import WaveRecord +from igor.record.variables import VariablesRecord +from igor.record.wave import WaveRecord _this_dir = os.path.dirname(__file__) @@ -1287,7 +1355,9 @@ def dumppxp(filename, strict=True): records = loadpxp(path, strict=strict) for i,record in enumerate(records): print('record {}:'.format(i)) - if isinstance(record, WaveRecord): + if isinstance(record, VariablesRecord): + pprint(record.variables) + elif isinstance(record, WaveRecord): pprint(record.wave) else: pprint(record) From 29b3524c6aea12300f5db5371e1d0bdbc7032f1e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 Jul 2012 08:31:11 -0400 Subject: [PATCH 25/76] Implement the plain-text HistoryRecord, RecreationRecord, and GetHistoryRecord. --- bin/igorbinarywave.py | 2 ++ igor/record/__init__.py | 4 +--- igor/record/base.py | 11 +++++++++++ igor/record/gethistory.py | 7 ------- igor/record/history.py | 12 ++++++++++-- igor/record/recreation.py | 7 ------- test/test.py | 11 +++++++---- 7 files changed, 31 insertions(+), 23 deletions(-) delete mode 100644 igor/record/gethistory.py delete mode 100644 igor/record/recreation.py diff --git a/bin/igorbinarywave.py b/bin/igorbinarywave.py index 435fdca..f3da1cd 100755 --- a/bin/igorbinarywave.py +++ b/bin/igorbinarywave.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +# +# Copyright "IBW -> ASCII conversion" diff --git a/igor/record/__init__.py b/igor/record/__init__.py index ffa6456..faaa61d 100644 --- a/igor/record/__init__.py +++ b/igor/record/__init__.py @@ -5,11 +5,9 @@ from .base import Record, UnknownRecord, UnusedRecord from .variables import VariablesRecord -from .history import HistoryRecord +from .history import HistoryRecord, RecreationRecord, GetHistoryRecord from .wave import WaveRecord -from .recreation import RecreationRecord from .procedure import ProcedureRecord -from .gethistory import GetHistoryRecord from .packedfile import PackedFileRecord from .folder import FolderStartRecord, FolderEndRecord diff --git a/igor/record/base.py b/igor/record/base.py index a6990f8..454e739 100644 --- a/igor/record/base.py +++ b/igor/record/base.py @@ -22,3 +22,14 @@ def __repr__(self): class UnusedRecord (Record): pass + + +# Copyright + +from .base import Record + + +class TextRecord (Record): + def __init__(self, *args, **kwargs): + super(TextRecord, self).__init__(*args, **kwargs) + self.text = str(self.data).replace('\r\n', '\n').replace('\r', '\n') diff --git a/igor/record/gethistory.py b/igor/record/gethistory.py deleted file mode 100644 index d2e5c20..0000000 --- a/igor/record/gethistory.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright - -from .base import Record - - -class GetHistoryRecord (Record): - pass diff --git a/igor/record/history.py b/igor/record/history.py index 0974cac..4d39b75 100644 --- a/igor/record/history.py +++ b/igor/record/history.py @@ -1,7 +1,15 @@ # Copyright -from .base import Record +from .base import TextRecord -class HistoryRecord (Record): +class HistoryRecord (TextRecord): + pass + + +class RecreationRecord (TextRecord): + pass + + +class GetHistoryRecord (TextRecord): pass diff --git a/igor/record/recreation.py b/igor/record/recreation.py deleted file mode 100644 index 3bc9cb4..0000000 --- a/igor/record/recreation.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright - -from .base import Record - - -class RecreationRecord (Record): - pass diff --git a/test/test.py b/test/test.py index 3fd208f..2051c9a 100644 --- a/test/test.py +++ b/test/test.py @@ -664,7 +664,7 @@ 'userStrs': {}, 'userVars': {}} record 31: - +'\x95 Polar Graphs Demo, v3.01\n\n' record 32: (array([ 0.30000001, 0.5448544 , 0.77480197, 0.97584349, 1.13573945, 1.24475539, 1.2962544 , 1.28710103, 1.21785283, 1.09272552, @@ -1321,9 +1321,9 @@ record 47: record 48: - +'| Platform=Windows95, IGORVersion=3.130\n\n\n\nMoveWindow/P 5.25,40.25,504.75,335\n...hook=PolarWindowHook\nEndMacro\n' record 49: - +'' record 50: """ @@ -1334,6 +1334,7 @@ from igor.binarywave import load as loadibw from igor.packed import load as loadpxp +from igor.record.base import TextRecord from igor.record.variables import VariablesRecord from igor.record.wave import WaveRecord @@ -1355,7 +1356,9 @@ def dumppxp(filename, strict=True): records = loadpxp(path, strict=strict) for i,record in enumerate(records): print('record {}:'.format(i)) - if isinstance(record, VariablesRecord): + if isinstance(record, TextRecord): + pprint(record.text) + elif isinstance(record, VariablesRecord): pprint(record.variables) elif isinstance(record, WaveRecord): pprint(record.wave) From 810a1a1a0bb73a2d7f2c1897ba4d63485d397c20 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 Jul 2012 08:33:14 -0400 Subject: [PATCH 26/76] Implement the plain-text ProcedureRecord. --- igor/record/procedure.py | 4 ++-- test/test.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/igor/record/procedure.py b/igor/record/procedure.py index 3c1e764..59c8610 100644 --- a/igor/record/procedure.py +++ b/igor/record/procedure.py @@ -1,7 +1,7 @@ # Copyright -from .base import Record +from .base import TextRecord -class ProcedureRecord (Record): +class ProcedureRecord (TextRecord): pass diff --git a/test/test.py b/test/test.py index 2051c9a..25a44eb 100644 --- a/test/test.py +++ b/test/test.py @@ -1325,7 +1325,7 @@ record 49: '' record 50: - +'#include version >= 3.0\n' """ import os.path From 309967f6c55e9f7892431de85a98561677d093e9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 Jul 2012 09:30:28 -0400 Subject: [PATCH 27/76] Implement the folder records and filesystem reconstruction. --- igor/packed.py | 61 ++++++++++++++++++++++++++++++++++++++++++- igor/record/base.py | 1 + igor/record/folder.py | 6 ++--- test/test.py | 34 ++++++++++++++++++------ 4 files changed, 90 insertions(+), 12 deletions(-) diff --git a/igor/packed.py b/igor/packed.py index 48f933e..10bf214 100644 --- a/igor/packed.py +++ b/igor/packed.py @@ -9,8 +9,13 @@ from .record import RECORD_TYPE as _RECORD_TYPE from .record.base import UnknownRecord as _UnknownRecord from .record.base import UnusedRecord as _UnusedRecord +from .record.folder import FolderStartRecord as _FolderStartRecord +from .record.folder import FolderEndRecord as _FolderEndRecord +from .record.variables import VariablesRecord as _VariablesRecord +from .record.wave import WaveRecord as _WaveRecord +# From PTN003: # Igor writes other kinds of records in a packed experiment file, for # storing things like pictures, page setup records, and miscellaneous # settings. The format for these records is quite complex and is not @@ -66,5 +71,59 @@ def load(filename, strict=True, ignore_unknown=True): if not hasattr(filename, 'read'): f.close() - return records + # From PTN003: + """The name must be a valid Igor data folder name. See Object + Names in the Igor Reference help file for name rules. + + When Igor Pro reads the data folder start record, it creates a new + data folder with the specified name. Any subsequent variable, wave + or data folder start records cause Igor to create data objects in + this new data folder, until Igor Pro reads a corresponding data + folder end record.""" + # From the Igor Manual, chapter 2, section 8, page II-123 + # http://www.wavemetrics.net/doc/igorman/II-08%20Data%20Folders.pdf + """Like the Macintosh file system, Igor Pro's data folders use the + colon character (:) to separate components of a path to an + object. This is analogous to Unix which uses / and Windows which + uses \. (Reminder: Igor's data folders exist wholly in memory + while an experiment is open. It is not a disk file system!) + + A data folder named "root" always exists and contains all other + data folders. + """ + # From the Igor Manual, chapter 4, page IV-2 + # http://www.wavemetrics.net/doc/igorman/IV-01%20Commands.pdf + """For waves and data folders only, you can also use "liberal" + names. Liberal names can include almost any character, including + spaces and dots (see Liberal Object Names on page III-415 for + details). + """ + # From the Igor Manual, chapter 3, section 16, page III-416 + # http://www.wavemetrics.net/doc/igorman/III-16%20Miscellany.pdf + """Liberal names have the same rules as standard names except you + may use any character except control characters and the following: + + " ' : ; + """ + filesystem = {'root': {}} + dir_stack = [('root', filesystem['root'])] + for record in records: + cwd = dir_stack[-1][-1] + if isinstance(record, _FolderStartRecord): + name = record.null_terminated_text + cwd[name] = {} + dir_stack.append((name, cwd[name])) + elif isinstance(record, _FolderEndRecord): + dir_stack.pop() + elif isinstance(record, (_VariablesRecord, _WaveRecord)): + if isinstance(record, _VariablesRecord): + filename = ':variables' # start with an invalid character + else: # to avoid collisions with folder + filename = ':waves' # names + if filename in cwd: + cwd[filename].append(record) + else: + cwd[filename] = [record] + + return (records, filesystem) diff --git a/igor/record/base.py b/igor/record/base.py index 454e739..53abe24 100644 --- a/igor/record/base.py +++ b/igor/record/base.py @@ -33,3 +33,4 @@ class TextRecord (Record): def __init__(self, *args, **kwargs): super(TextRecord, self).__init__(*args, **kwargs) self.text = str(self.data).replace('\r\n', '\n').replace('\r', '\n') + self.null_terminated_text = self.text.split('\x00', 1)[0] diff --git a/igor/record/folder.py b/igor/record/folder.py index b03e283..be98c58 100644 --- a/igor/record/folder.py +++ b/igor/record/folder.py @@ -1,11 +1,11 @@ # Copyright -from .base import Record +from .base import TextRecord -class FolderStartRecord (Record): +class FolderStartRecord (TextRecord): pass -class FolderEndRecord (Record): +class FolderEndRecord (TextRecord): pass diff --git a/test/test.py b/test/test.py index 25a44eb..bf8d6de 100644 --- a/test/test.py +++ b/test/test.py @@ -1248,9 +1248,9 @@ 'whpad3': 0, 'whpad4': 0}) record 40: - +'Packages' record 41: - +'WMDataBase' record 42: {'header': {'numSysVars': 21, 'numUserStrs': 6, @@ -1267,9 +1267,9 @@ 'u_str': '2'}, 'userVars': {}} record 43: - +'' record 44: - +'PolarGraphs' record 45: {'header': {'numSysVars': 21, 'numUserStrs': 10, @@ -1317,15 +1317,28 @@ 'u_y1': 42.732577139459856, 'u_y2': 45.081649278814126}} record 46: - +'' record 47: - +'' record 48: '| Platform=Windows95, IGORVersion=3.130\n\n\n\nMoveWindow/P 5.25,40.25,504.75,335\n...hook=PolarWindowHook\nEndMacro\n' record 49: '' record 50: '#include version >= 3.0\n' + +filesystem: +{'root': {':variables': [], + ':waves': [, + , + , + , + , + , + , + ], + 'Packages': {'PolarGraphs': {':variables': []}, + 'WMDataBase': {':variables': []}}}} """ import os.path @@ -1335,6 +1348,7 @@ from igor.binarywave import load as loadibw from igor.packed import load as loadpxp from igor.record.base import TextRecord +from igor.record.folder import FolderStartRecord, FolderEndRecord from igor.record.variables import VariablesRecord from igor.record.wave import WaveRecord @@ -1353,10 +1367,12 @@ def dumpibw(filename, strict=True): def dumppxp(filename, strict=True): sys.stderr.write('Testing {}\n'.format(filename)) path = os.path.join(_data_dir, filename) - records = loadpxp(path, strict=strict) + records,filesystem = loadpxp(path, strict=strict) for i,record in enumerate(records): print('record {}:'.format(i)) - if isinstance(record, TextRecord): + if isinstance(record, (FolderStartRecord, FolderEndRecord)): + pprint(record.null_terminated_text) + elif isinstance(record, TextRecord): pprint(record.text) elif isinstance(record, VariablesRecord): pprint(record.variables) @@ -1364,6 +1380,8 @@ def dumppxp(filename, strict=True): pprint(record.wave) else: pprint(record) + print('\nfilesystem:') + pprint(filesystem) def pprint(data): lines = pformat(data).splitlines() From 6c2c99bb35c9bbd3fab649403029e1f46747fe02 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 Jul 2012 09:56:47 -0400 Subject: [PATCH 28/76] Read sIndices as uint32's in binarywave.load. --- igor/binarywave.py | 17 +++++++++++++---- test/test.py | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index 4fa5e4a..e7f1282 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -382,20 +382,29 @@ def load(filename, strict=True): labels = str(f.read(size)).split(chr(0)) # split null-delimited strings bin_info['dimLabels'].append([L for L in labels if len(L) > 0]) if wave_info['type'] == 0: # text wave - bin_info['sIndices'] = f.read(bin_info['sIndicesSize']) + sIndices_data = f.read(bin_info['sIndicesSize']) + sIndices_count = bin_info['sIndicesSize']/4 + bin_info['sIndices'] = _numpy.ndarray( + shape=(sIndices_count,), + dtype=_numpy.dtype( + _numpy.uint32()).newbyteorder(byteOrder), + buffer=sIndices_data, + order='F', + ) if wave_info['type'] == 0: # text wave # use sIndices to split data into strings strings = [] start = 0 - for i,string_index in enumerate(bin_info['sIndices']): - offset = ord(string_index) + for i,offset in enumerate(bin_info['sIndices']): if offset > start: string = data[start:offset] strings.append(''.join(chr(x) for x in string)) start = offset + elif offset == start: + strings.append('') else: - assert offset == 0, offset + raise ValueError((offset, bin_info['sIndices'])) data = _numpy.array(strings) shape = [n for n in wave_info['nDim'] if n > 0] or (0,) data.reshape(shape) diff --git a/test/test.py b/test/test.py index bf8d6de..cda1355 100644 --- a/test/test.py +++ b/test/test.py @@ -58,7 +58,7 @@ 'noteSize': 0, 'optionsSize1': 0, 'optionsSize2': 0, - 'sIndices': '\x00\x00\x00\x04\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x0e\x00\x00\x00\x12', + 'sIndices': array([ 4, 7, 8, 14, 18], dtype=uint32), 'sIndicesSize': 20, 'version': 5, 'wfmSize': 338} @@ -374,7 +374,7 @@ 'noteSize': 0, 'optionsSize1': 0, 'optionsSize2': 0, - 'sIndices': '\x04\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00\x0e\x00\x00\x00\x12\x00\x00\x00', + 'sIndices': array([ 4, 7, 8, 14, 18], dtype=uint32), 'sIndicesSize': 20, 'version': 5, 'wfmSize': 338} From 8574937ba2e351820597b6c100bb48af1822de3b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 Jul 2012 10:20:53 -0400 Subject: [PATCH 29/76] Store wave, bin_info, and wave_info separately in WaveRecord. This makes more sense than storing them as a tuple. Also, use wave_record.wave_info['bname'] as the name for storing the wave in the filesystem view returned by packed.load. --- igor/packed.py | 14 +- igor/record/wave.py | 3 +- test/test.py | 776 ++++++++++++++++++++++---------------------- 3 files changed, 400 insertions(+), 393 deletions(-) diff --git a/igor/packed.py b/igor/packed.py index 10bf214..f6c8adf 100644 --- a/igor/packed.py +++ b/igor/packed.py @@ -117,13 +117,17 @@ def load(filename, strict=True, ignore_unknown=True): dir_stack.pop() elif isinstance(record, (_VariablesRecord, _WaveRecord)): if isinstance(record, _VariablesRecord): - filename = ':variables' # start with an invalid character - else: # to avoid collisions with folder - filename = ':waves' # names + # start with an invalid character to avoid collisions + # with folder names + filename = ':variables' + else: + filename = ''.join(c for c in record.wave_info['bname'] + ).split('\x00', 1)[0] if filename in cwd: - cwd[filename].append(record) + raise ValueError('collision on name {} in {}'.format( + filename, ':'.join(d for d,cwd in dir_stack))) else: - cwd[filename] = [record] + cwd[filename] = record return (records, filesystem) diff --git a/igor/record/wave.py b/igor/record/wave.py index 53d3af1..93de8a3 100644 --- a/igor/record/wave.py +++ b/igor/record/wave.py @@ -9,7 +9,8 @@ class WaveRecord (Record): def __init__(self, *args, **kwargs): super(WaveRecord, self).__init__(*args, **kwargs) - self.wave = _loadibw(_BytesIO(bytes(self.data)), strict=False) + self.wave,self.bin_info,self.wave_info = _loadibw( + _BytesIO(bytes(self.data)), strict=False) def __str__(self): return str(self.wave) diff --git a/test/test.py b/test/test.py index cda1355..c2911f7 100644 --- a/test/test.py +++ b/test/test.py @@ -666,7 +666,7 @@ record 31: '\x95 Polar Graphs Demo, v3.01\n\n' record 32: -(array([ 0.30000001, 0.5448544 , 0.77480197, 0.97584349, 1.13573945, +array([ 0.30000001, 0.5448544 , 0.77480197, 0.97584349, 1.13573945, 1.24475539, 1.2962544 , 1.28710103, 1.21785283, 1.09272552, 0.91933674, 0.7082426 , 0.47229454, 0.22585714, -0.01606643, -0.23874778, -0.42862982, -0.57415301, -0.6664573 , -0.69992352, @@ -691,46 +691,46 @@ 1.17415261, 1.0286293 , 0.83874667, 0.61606491, 0.37414294, 0.12770344, -0.1082412 , -0.31933719, -0.49272597, -0.61785328, -0.6871013 , -0.69625437, -0.64475471, -0.53574032, -0.37584305, - -0.17479956, 0.05514668, 0.30000135], dtype=float32), - {'checksum': -25004, - 'note': '', - 'noteSize': 0, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 638}, - {'aModified': 0, - 'bname': array(['r', 'a', 'd', 'i', 'u', 's', 'D', 'a', 't', 'a', '', '', '', '', + -0.17479956, 0.05514668, 0.30000135], dtype=float32) +{'checksum': -25004, + 'note': '', + 'noteSize': 0, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 638} +{'aModified': 0, + 'bname': array(['r', 'a', 'd', 'i', 'u', 's', 'D', 'a', 't', 'a', '', '', '', '', '', '', '', '', '', ''], dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 0.04908738521234052, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845545774, - 'next': 0, - 'npnts': 128, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': array([ 0.30000001, 0.5448544 , 0.77480197, 0.97584349]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}) + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 0.04908738521234052, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845545774, + 'next': 0, + 'npnts': 128, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': array([ 0.30000001, 0.5448544 , 0.77480197, 0.97584349]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')} record 33: -(array([ 0. , 0.0494739 , 0.0989478 , 0.1484217 , 0.1978956 , +array([ 0. , 0.0494739 , 0.0989478 , 0.1484217 , 0.1978956 , 0.24736951, 0.29684341, 0.34631732, 0.3957912 , 0.44526511, 0.49473903, 0.54421294, 0.59368682, 0.6431607 , 0.69263464, 0.74210852, 0.79158241, 0.84105635, 0.89053023, 0.94000411, @@ -755,46 +755,46 @@ 5.44212914, 5.4916029 , 5.54107714, 5.5905509 , 5.64002466, 5.6894989 , 5.73897219, 5.78844643, 5.83792019, 5.88739443, 5.93686819, 5.98634195, 6.03581619, 6.08528948, 6.13476372, - 6.18423796, 6.23371172, 6.28318548], dtype=float32), - {'checksum': 28621, - 'note': '', - 'noteSize': 0, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 638}, - {'aModified': 0, - 'bname': array(['a', 'n', 'g', 'l', 'e', 'D', 'a', 't', 'a', '', '', '', '', '', '', + 6.18423796, 6.23371172, 6.28318548], dtype=float32) +{'checksum': 28621, + 'note': '', + 'noteSize': 0, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 638} +{'aModified': 0, + 'bname': array(['a', 'n', 'g', 'l', 'e', 'D', 'a', 't', 'a', '', '', '', '', '', '', '', '', '', '', ''], dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 0.04908738521234052, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845470039, - 'next': 0, - 'npnts': 128, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': array([ 0. , 0.0494739, 0.0989478, 0.1484217]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}) + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 0.04908738521234052, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845470039, + 'next': 0, + 'npnts': 128, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': array([ 0. , 0.0494739, 0.0989478, 0.1484217]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')} record 34: -(array([ 1.83690956e-17, 2.69450769e-02, 7.65399113e-02, +array([ 1.83690956e-17, 2.69450769e-02, 7.65399113e-02, 1.44305170e-01, 2.23293692e-01, 3.04783821e-01, 3.79158467e-01, 4.36888516e-01, 4.69528973e-01, 4.70633775e-01, 4.36502904e-01, 3.66688997e-01, @@ -836,70 +836,70 @@ 1.51621893e-01, 2.12215677e-01, 2.38205954e-01, 2.33226836e-01, 2.03656554e-01, 1.57870770e-01, 1.05330117e-01, 5.55786416e-02, 1.72677450e-02, - -2.72719120e-03, 5.24539061e-08], dtype=float32), - {'checksum': 23021, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': 'PolarRadiusFunction(radiusData,1,0) * cos(PolarAngleFunction(angleData,3,1,2))\x00', - 'formulaSize': 80, - 'note': '', - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 832}, - {'aModified': 0, - 'bname': array(['W', '_', 'p', 'l', 'r', 'X', '5', '', '', '', '', '', '', '', '', + -2.72719120e-03, 5.24539061e-08], dtype=float32) +{'checksum': 23021, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': 'PolarRadiusFunction(radiusData,1,0) * cos(PolarAngleFunction(angleData,3,1,2))\x00', + 'formulaSize': 80, + 'note': '', + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 832} +{'aModified': 0, + 'bname': array(['W', '_', 'p', 'l', 'r', 'X', '5', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 24, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 24, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 0, - 'formula': 8054500, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([128, 0, 0, 0]), - 'next': 8054516, - 'npnts': 128, - 'sIndices': 0, - 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 1.8369095638207904e-17, - 'wModified': 0, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}) + 'fileName': 0, + 'formula': 8054500, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([128, 0, 0, 0]), + 'next': 8054516, + 'npnts': 128, + 'sIndices': 0, + 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 1.8369095638207904e-17, + 'wModified': 0, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0} record 35: -(array([ 0.30000001, 0.54418772, 0.77101213, 0.96511477, 1.1135726 , +array([ 0.30000001, 0.54418772, 0.77101213, 0.96511477, 1.1135726 , 1.20686483, 1.23956215, 1.21068466, 1.12370288, 0.98618096, 0.80910152, 0.60592639, 0.39147732, 0.18073183, -0.01236418, -0.17596789, -0.30120692, -0.38277394, -0.41920158, -0.41280419, @@ -924,70 +924,70 @@ 0.78277934, 0.72283876, 0.6181944 , 0.47410288, 0.29939076, 0.10585135, -0.09260413, -0.28104633, -0.44468346, -0.57008827, -0.64630753, -0.66580337, -0.62512833, -0.52528399, -0.37171093, - -0.17394456, 0.0550792 , 0.30000135], dtype=float32), - {'checksum': -9146, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': 'PolarRadiusFunction(radiusData,1,0) * sin(PolarAngleFunction(angleData,3,1,2))\x00', - 'formulaSize': 80, - 'note': 'shadowX=W_plrX5,appendRadius=radiusData,appendAngleData=angleData,angleDataUnits=2', - 'noteSize': 82, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 832}, - {'aModified': 0, - 'bname': array(['W', '_', 'p', 'l', 'r', 'Y', '5', '', '', '', '', '', '', '', '', + -0.17394456, 0.0550792 , 0.30000135], dtype=float32) +{'checksum': -9146, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': 'PolarRadiusFunction(radiusData,1,0) * sin(PolarAngleFunction(angleData,3,1,2))\x00', + 'formulaSize': 80, + 'note': 'shadowX=W_plrX5,appendRadius=radiusData,appendAngleData=angleData,angleDataUnits=2', + 'noteSize': 82, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 832} +{'aModified': 0, + 'bname': array(['W', '_', 'p', 'l', 'r', 'Y', '5', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 26, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 26, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 0, - 'formula': 8054532, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([128, 0, 0, 0]), - 'next': 8084972, - 'npnts': 128, - 'sIndices': 0, - 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 0.30000001192092896, - 'wModified': 0, - 'waveNoteH': 7996608, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}) + 'fileName': 0, + 'formula': 8054532, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([128, 0, 0, 0]), + 'next': 8084972, + 'npnts': 128, + 'sIndices': 0, + 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 0.30000001192092896, + 'wModified': 0, + 'waveNoteH': 7996608, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0} record 36: -(array([ 0.2617994 , 0.27842158, 0.29504377, 0.31166595, 0.32828814, +array([ 0.2617994 , 0.27842158, 0.29504377, 0.31166595, 0.32828814, 0.34491032, 0.36153251, 0.3781547 , 0.39477688, 0.41139907, 0.42802125, 0.44464344, 0.46126559, 0.47788778, 0.49450997, 0.51113212, 0.52775431, 0.54437649, 0.56099868, 0.57762086, @@ -999,46 +999,46 @@ 1.00979757, 1.02641988, 1.04304194, 1.05966425, 1.07628632, 1.09290862, 1.10953069, 1.12615299, 1.14277506, 1.15939736, 1.17601943, 1.19264174, 1.2092638 , 1.22588611, 1.24250817, - 1.25913048, 1.27575254, 1.29237485, 1.30899692], dtype=float32), - {'checksum': 14307, - 'note': '', - 'noteSize': 0, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 382}, - {'aModified': 0, - 'bname': array(['a', 'n', 'g', 'l', 'e', 'Q', '1', '', '', '', '', '', '', '', '', + 1.25913048, 1.27575254, 1.29237485, 1.30899692], dtype=float32) +{'checksum': 14307, + 'note': '', + 'noteSize': 0, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 382} +{'aModified': 0, + 'bname': array(['a', 'n', 'g', 'l', 'e', 'Q', '1', '', '', '', '', '', '', '', '', '', '', '', '', ''], dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845473705, - 'next': 0, - 'npnts': 64, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': array([ 0.2617994 , 0.27842158, 0.29504377, 0.31166595]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}) + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845473705, + 'next': 0, + 'npnts': 64, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': array([ 0.2617994 , 0.27842158, 0.29504377, 0.31166595]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')} record 37: -(array([ -8.34064484, -7.66960144, -6.62294245, -6.82878971, +array([ -8.34064484, -7.66960144, -6.62294245, -6.82878971, -8.6383152 , -11.20019722, -13.83398628, -15.95139503, -16.18096733, -13.58062267, -9.26843071, -5.34649038, -3.01010084, -2.30953455, -2.73682952, -3.72112942, @@ -1053,46 +1053,46 @@ -4.54975414, -4.52917624, -3.99160147, -3.1971693 , -2.93472862, -3.47230864, -4.7322526 , -6.80173016, -9.08601665, -10.00928402, -8.87677383, -6.88120317, - -5.61007977, -5.6351161 , -6.41880989, -6.8738699 ], dtype=float32), - {'checksum': -12080, - 'note': '', - 'noteSize': 0, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 382}, - {'aModified': 0, - 'bname': array(['r', 'a', 'd', 'i', 'u', 's', 'Q', '1', '', '', '', '', '', '', '', + -5.61007977, -5.6351161 , -6.41880989, -6.8738699 ], dtype=float32) +{'checksum': -12080, + 'note': '', + 'noteSize': 0, + 'pictSize': 0, + 'version': 2, + 'wfmSize': 382} +{'aModified': 0, + 'bname': array(['r', 'a', 'd', 'i', 'u', 's', 'Q', '1', '', '', '', '', '', '', '', '', '', '', '', ''], dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845473634, - 'next': 0, - 'npnts': 64, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': array([-8.34064484, -7.66960144, -6.62294245, -6.82878971]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')}) + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845473634, + 'next': 0, + 'npnts': 64, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': array([-8.34064484, -7.66960144, -6.62294245, -6.82878971]), + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')} record 38: -(array([ 30.58058929, 31.08536911, 31.93481636, 31.57315445, +array([ 30.58058929, 31.08536911, 31.93481636, 31.57315445, 29.68683434, 27.10366058, 24.47453499, 22.3495121 , 21.98692894, 24.21500397, 27.95923996, 31.28394508, 33.12408066, 33.46794128, 32.79909515, 31.64211464, @@ -1107,70 +1107,70 @@ 17.34101677, 16.83446693, 16.56042671, 16.38027191, 15.94310474, 15.16159916, 14.10328865, 12.76812935, 11.41363049, 10.60795975, 10.52314186, 10.67826462, - 10.5454855 , 9.99268055, 9.22939587, 8.5736742 ], dtype=float32), - {'checksum': -5745, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': 'PolarRadiusFunction(radiusQ1,1,-40) * cos(PolarAngleFunction(angleQ1,2,2,2))\x00', - 'formulaSize': 78, - 'note': '', - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 576}, - {'aModified': 0, - 'bname': array(['W', '_', 'p', 'l', 'r', 'X', '6', '', '', '', '', '', '', '', '', + 10.5454855 , 9.99268055, 9.22939587, 8.5736742 ], dtype=float32) +{'checksum': -5745, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': 'PolarRadiusFunction(radiusQ1,1,-40) * cos(PolarAngleFunction(angleQ1,2,2,2))\x00', + 'formulaSize': 78, + 'note': '', + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 576} +{'aModified': 0, + 'bname': array(['W', '_', 'p', 'l', 'r', 'X', '6', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 30, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 30, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 0, - 'formula': 8052116, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([64, 0, 0, 0]), - 'next': 8324392, - 'npnts': 64, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 30.580589294433594, - 'wModified': 0, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}) + 'fileName': 0, + 'formula': 8052116, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([64, 0, 0, 0]), + 'next': 8324392, + 'npnts': 64, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 30.580589294433594, + 'wModified': 0, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0} record 39: -(array([ 8.19404411, 8.88563347, 9.70543861, 10.17177773, +array([ 8.19404411, 8.88563347, 9.70543861, 10.17177773, 10.11173058, 9.73756695, 9.25513077, 8.8788929 , 9.16085339, 10.56489944, 12.75579453, 14.90572262, 16.46352959, 17.33401871, 17.68511391, 17.74635315, @@ -1185,68 +1185,68 @@ 30.91939545, 31.22146797, 31.97431755, 32.95656204, 33.4611969 , 33.23248672, 32.3250885 , 30.64473915, 28.72983551, 28.05199242, 29.29024887, 31.3501091 , - 32.7331543 , 32.87995529, 32.28799438, 31.99738503], dtype=float32), - {'checksum': -16604, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': 'PolarRadiusFunction(radiusQ1,1,-40) * sin(PolarAngleFunction(angleQ1,2,2,2))\x00', - 'formulaSize': 78, - 'note': 'shadowX=W_plrX6,appendRadius=radiusQ1,appendAngleData=angleQ1,angleDataUnits=2', - 'noteSize': 78, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 576}, - {'aModified': 0, - 'bname': array(['W', '_', 'p', 'l', 'r', 'Y', '6', '', '', '', '', '', '', '', '', + 32.7331543 , 32.87995529, 32.28799438, 31.99738503], dtype=float32) +{'checksum': -16604, + 'dataEUnits': '', + 'dataEUnitsSize': 0, + 'dimEUnits': ['', '', '', ''], + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabels': [[], [], [], []], + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formula': 'PolarRadiusFunction(radiusQ1,1,-40) * sin(PolarAngleFunction(angleQ1,2,2,2))\x00', + 'formulaSize': 78, + 'note': 'shadowX=W_plrX6,appendRadius=radiusQ1,appendAngleData=angleQ1,angleDataUnits=2', + 'noteSize': 78, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'version': 5, + 'wfmSize': 576} +{'aModified': 0, + 'bname': array(['W', '_', 'p', 'l', 'r', 'Y', '6', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 32, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 32, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 0, - 'formula': 7995612, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([64, 0, 0, 0]), - 'next': 0, - 'npnts': 64, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 8.19404411315918, - 'wModified': 0, - 'waveNoteH': 7998208, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0}) + 'fileName': 0, + 'formula': 7995612, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([64, 0, 0, 0]), + 'next': 0, + 'npnts': 64, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wData': 8.19404411315918, + 'wModified': 0, + 'waveNoteH': 7998208, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0} record 40: 'Packages' record 41: @@ -1328,17 +1328,17 @@ '#include version >= 3.0\n' filesystem: -{'root': {':variables': [], - ':waves': [, - , - , - , - , - , - , - ], - 'Packages': {'PolarGraphs': {':variables': []}, - 'WMDataBase': {':variables': []}}}} +{'root': {':variables': , + 'Packages': {'PolarGraphs': {':variables': }, + 'WMDataBase': {':variables': }}, + 'W_plrX5': , + 'W_plrX6': , + 'W_plrY5': , + 'W_plrY6': , + 'angleData': , + 'angleQ1': , + 'radiusData': , + 'radiusQ1': }} """ import os.path @@ -1378,6 +1378,8 @@ def dumppxp(filename, strict=True): pprint(record.variables) elif isinstance(record, WaveRecord): pprint(record.wave) + pprint(record.bin_info) + pprint(record.wave_info) else: pprint(record) print('\nfilesystem:') From 55a8cfc9ae42480191926c3e93318fb38bd3784a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 Jul 2012 10:43:16 -0400 Subject: [PATCH 30/76] Use variable names to store variables in the filesystem (vs. VariablesRecord). --- igor/packed.py | 42 +++++++++++++++++++++++++------ test/test.py | 68 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 10 deletions(-) diff --git a/igor/packed.py b/igor/packed.py index f6c8adf..baad25e 100644 --- a/igor/packed.py +++ b/igor/packed.py @@ -71,6 +71,11 @@ def load(filename, strict=True, ignore_unknown=True): if not hasattr(filename, 'read'): f.close() + filesystem = _build_filesystem(records) + + return (records, filesystem) + +def _build_filesystem(records): # From PTN003: """The name must be a valid Igor data folder name. See Object Names in the Igor Reference help file for name rules. @@ -117,17 +122,40 @@ def load(filename, strict=True, ignore_unknown=True): dir_stack.pop() elif isinstance(record, (_VariablesRecord, _WaveRecord)): if isinstance(record, _VariablesRecord): + _add_variables(dir_stack, cwd, record) # start with an invalid character to avoid collisions # with folder names - filename = ':variables' - else: + #filename = ':variables' + #_check_filename(dir_stack, filename) + #cwd[filename] = record + else: # WaveRecord filename = ''.join(c for c in record.wave_info['bname'] ).split('\x00', 1)[0] - if filename in cwd: - raise ValueError('collision on name {} in {}'.format( - filename, ':'.join(d for d,cwd in dir_stack))) - else: + _check_filename(dir_stack, filename) cwd[filename] = record + return filesystem - return (records, filesystem) +def _check_filename(dir_stack, filename): + cwd = dir_stack[-1][-1] + if filename in cwd: + raise ValueError('collision on name {} in {}'.format( + filename, ':'.join(d for d,cwd in dir_stack))) +def _add_variables(dir_stack, cwd, record): + if len(dir_stack) == 1: + # From PTN003: + """When reading a packed file, any system variables + encountered while the current data folder is not the root + should be ignored. + """ + for i,value in enumerate(record.variables['sysVars']): + name = 'K{}'.format(i) + _check_filename(dir_stack, name) + cwd[name] = value + for name,value in ( + record.variables['userVars'].items() + + record.variables['userStrs'].items()): + _check_filename(dir_stack, name) + cwd[name] = value + if record.variables['header']['version'] == 2: + raise NotImplementedError('add dependent variables to filesystem') diff --git a/test/test.py b/test/test.py index c2911f7..af65c79 100644 --- a/test/test.py +++ b/test/test.py @@ -1328,9 +1328,71 @@ '#include version >= 3.0\n' filesystem: -{'root': {':variables': , - 'Packages': {'PolarGraphs': {':variables': }, - 'WMDataBase': {':variables': }}, +{'root': {'K0': 0.0, + 'K1': 0.0, + 'K10': 0.0, + 'K11': 0.0, + 'K12': 0.0, + 'K13': 0.0, + 'K14': 0.0, + 'K15': 0.0, + 'K16': 0.0, + 'K17': 0.0, + 'K18': 0.0, + 'K19': 0.0, + 'K2': 0.0, + 'K20': 128.0, + 'K3': 0.0, + 'K4': 0.0, + 'K5': 0.0, + 'K6': 0.0, + 'K7': 0.0, + 'K8': 0.0, + 'K9': 0.0, + 'Packages': {'PolarGraphs': {'V_bottom': 232.0, + 'V_left': 1.0, + 'V_max': 2.4158518093414401, + 'V_min': -2.1848498883412, + 'V_right': 232.0, + 'V_top': 1.0, + 'u_UniqWaveNdx': 8.0, + 'u_UniqWinNdx': 3.0, + 'u_angle0': 0.0, + 'u_angleRange': 6.2831853071795862, + 'u_colorList': 'black;blue;green;cyan;red;magenta;yellow;white;special', + 'u_debug': 0.0, + 'u_debugStr': 'Turn Debugging On', + 'u_majorDelta': 0.0, + 'u_numPlaces': 0.0, + 'u_polAngle0': 0.26179938779914941, + 'u_polAngleAxesWherePop': 'Off;Radius Start;Radius End;Radius Start and End;All Major Radii;At Listed Radii', + 'u_polAngleRange': 1.0471975511965976, + 'u_polAngleUnitsPop': 'deg;rad', + 'u_polInnerRadius': -20.0, + 'u_polLineStylePop': 'solid;dash 1;dash 2;dash 3;dash 4;dash 5;dash 6;dash 7;dash 8;dash 9;dash 10;dash 11;dash 12;dash 13;dash 14;dash 15;dash 16;dash 17;', + 'u_polMajorAngleInc': 0.26179938779914941, + 'u_polMajorRadiusInc': 10.0, + 'u_polMinorAngleTicks': 3.0, + 'u_polMinorRadiusTicks': 1.0, + 'u_polOffOn': 'Off;On', + 'u_polOuterRadius': 0.0, + 'u_polRadAxesWherePop': ' Off; Angle Start; Angle Middle; Angle End; Angle Start and End; 0; 90; 180; -90; 0, 90; 90, 180; -180, -90; -90, 0; 0, 180; 90, -90; 0, 90, 180, -90; All Major Angles; At Listed Angles', + 'u_polRotPop': ' -90; 0; +90; +180', + 'u_popup': '', + 'u_prompt': '', + 'u_segsPerMinorArc': 3.0, + 'u_tickDelta': 0.0, + 'u_var': 0.0, + 'u_x1': 11.450159535018935, + 'u_x2': 12.079591517721363, + 'u_y1': 42.732577139459856, + 'u_y2': 45.081649278814126}, + 'WMDataBase': {'u_dataBase': ';PolarGraph0:,appendRadius=radiusData,...,useCircles=2,maxArcLine=6;', + 'u_dbBadStringChars': ',;=:', + 'u_dbCurrBag': 'PolarGraph1', + 'u_dbCurrContents': ',appendRadius=radiusQ1,...,useCircles=2,maxArcLine=6;', + 'u_dbReplaceBadChars': '\xa9\xae\x99\x9f', + 'u_str': '2'}}, 'W_plrX5': , 'W_plrX6': , 'W_plrY5': , From 11db2c41801a924c541d84e2717e15433508ba30 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 Jul 2012 10:49:40 -0400 Subject: [PATCH 31/76] fix record.base --- igor/record/base.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/igor/record/base.py b/igor/record/base.py index 53abe24..f173d20 100644 --- a/igor/record/base.py +++ b/igor/record/base.py @@ -24,11 +24,6 @@ class UnusedRecord (Record): pass -# Copyright - -from .base import Record - - class TextRecord (Record): def __init__(self, *args, **kwargs): super(TextRecord, self).__init__(*args, **kwargs) From 5375c9477d12aaf0b83e7d0d0bf0873c475c7e64 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 20 Jul 2012 14:46:08 -0400 Subject: [PATCH 32/76] Add igor.LOG for easier debugging. --- igor/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/igor/__init__.py b/igor/__init__.py index f7ea88f..816051c 100644 --- a/igor/__init__.py +++ b/igor/__init__.py @@ -3,3 +3,13 @@ "Interface for reading binary IGOR files." __version__ = '0.2' + + +import logging as _logging + + +LOG = _logging.getLogger('igor') +LOG.setLevel(_logging.ERROR) +LOG.addHandler(_logging.StreamHandler()) +LOG.handlers[-1].setFormatter( + _logging.Formatter('%(name)s - %(levelname)s - %(message)s')) From bdfe5f019373d5e292cdfa90062444ad28db104a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 20 Jul 2012 14:50:09 -0400 Subject: [PATCH 33/76] Rework igor.struct to support dynamic structures. Between binarywave and record.variables, there was a good deal of parsing that was conditional on previously parsed data. Rather than continue writing spaghetti code to handle each specific case, I've taken a stab at a general framework for updating structures during parsing (DynamicStructure and DynamicField). The docstrings should explain how they work. The implementation still has a few holes, but it works on each of the files in my test suite. --- igor/binarywave.py | 605 +++++++---- igor/packed.py | 57 +- igor/record/variables.py | 365 ++++--- igor/record/wave.py | 3 +- igor/struct.py | 441 +++++++- test/test.py | 2040 +++++++++++++++++++------------------- 6 files changed, 2083 insertions(+), 1428 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index e7f1282..ee70be2 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -26,14 +26,19 @@ # share. We hope IGOR Technical Notes will provide you with lots of # valuable information while you are developing IGOR applications. +from __future__ import absolute_import import array as _array +import struct as _struct import sys as _sys import types as _types import numpy as _numpy +from . import LOG as _LOG from .struct import Structure as _Structure +from .struct import DynamicStructure as _DynamicStructure from .struct import Field as _Field +from .struct import DynamicField as _DynamicField from .util import assert_null as _assert_null from .util import byte_order as _byte_order from .util import need_to_reorder_bytes as _need_to_reorder_bytes @@ -55,6 +60,40 @@ complexUInt32 = _numpy.dtype( [('real', _numpy.uint32), ('imag', _numpy.uint32)]) + +class StaticStringField (_DynamicField): + _null_terminated = False + _array_size_field = None + def post_unpack(self, parents, data): + wave_structure = parents[-1] + wave_data = self._get_structure_data(parents, data, wave_structure) + d = self._normalize_string(wave_data[self.name]) + wave_data[self.name] = d + + def _normalize_string(self, d): + if hasattr(d, 'tostring'): + d = d.tostring() + else: + d = ''.join(d) + if self._array_size_field: + start = 0 + strings = [] + for count in self.counts: + end = start + count + if end > start: + strings.append(d[start:end]) + if self._null_terminated: + strings[-1] = strings[-1].split('\x00', 1)[0] + start = end + elif self._null_terminated: + d = d.split('\x00', 1)[0] + return d + + +class NullStaticStringField (StaticStringField): + _null_terminated = True + + # Begin IGOR constants and typedefs from IgorBin.h # From IgorMath.h @@ -88,45 +127,35 @@ MAXDIMS = 4 # From binary.h -BinHeaderCommon = _Structure( # WTK: this one is mine. - name='BinHeaderCommon', - fields=[ - _Field('h', 'version', help='Version number for backwards compatibility.'), - ]) - -BinHeader1 = _Structure( +BinHeader1 = _Structure( # `version` field pulled out into Wave name='BinHeader1', fields=[ - _Field('h', 'version', help='Version number for backwards compatibility.'), _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), _Field('h', 'checksum', help='Checksum over this header and the wave header.'), ]) -BinHeader2 = _Structure( +BinHeader2 = _Structure( # `version` field pulled out into Wave name='BinHeader2', fields=[ - _Field('h', 'version', help='Version number for backwards compatibility.'), _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), _Field('l', 'noteSize', help='The size of the note text.'), _Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'), _Field('h', 'checksum', help='Checksum over this header and the wave header.'), ]) -BinHeader3 = _Structure( +BinHeader3 = _Structure( # `version` field pulled out into Wave name='BinHeader3', fields=[ - _Field('h', 'version', help='Version number for backwards compatibility.'), - _Field('h', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), + _Field('l', 'wfmSize', help='The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding.'), _Field('l', 'noteSize', help='The size of the note text.'), _Field('l', 'formulaSize', help='The size of the dependency formula, if any.'), _Field('l', 'pictSize', default=0, help='Reserved. Write zero. Ignore on read.'), _Field('h', 'checksum', help='Checksum over this header and the wave header.'), ]) -BinHeader5 = _Structure( +BinHeader5 = _Structure( # `version` field pulled out into Wave name='BinHeader5', fields=[ - _Field('h', 'version', help='Version number for backwards compatibility.'), _Field('h', 'checksum', help='Checksum over this header and the wave header.'), _Field('l', 'wfmSize', help='The size of the WaveHeader5 data structure plus the wave data.'), _Field('l', 'formulaSize', help='The size of the dependency formula, if any.'), @@ -149,12 +178,13 @@ # Header to an array of waveform data. -WaveHeader2 = _Structure( +# `wData` field pulled out into DynamicWaveDataField1 +WaveHeader2 = _DynamicStructure( name='WaveHeader2', fields=[ _Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'), _Field('P', 'next', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME2+2), + NullStaticStringField('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME2+2), _Field('h', 'whVersion', default=0, help='Write 0. Ignore on read.'), _Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'), _Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'), @@ -177,10 +207,12 @@ _Field('c', 'wUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=2), _Field('L', 'modDate', help='DateTime of last modification.'), _Field('P', 'waveNoteH', help='Used in memory only. Write zero. Ignore on read.'), - _Field('f', 'wData', help='The start of the array of waveform data.', count=4), ]) -WaveHeader5 = _Structure( +# `sIndices` pointer unset (use Wave5_data['sIndices'] instead). This +# field is filled in by DynamicStringIndicesDataField. +# `wData` field pulled out into DynamicWaveDataField5 +WaveHeader5 = _DynamicStructure( name='WaveHeader5', fields=[ _Field('P', 'next', help='link to next wave in linked list.'), @@ -191,7 +223,7 @@ _Field('h', 'dLock', default=0, help='Reserved. Write zero. Ignore on read.'), _Field('c', 'whpad1', default=0, help='Reserved. Write zero. Ignore on read.', count=6), _Field('h', 'whVersion', default=1, help='Write 1. Ignore on read.'), - _Field('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME5+1), + NullStaticStringField('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME5+1), _Field('l', 'whpad2', default=0, help='Reserved. Write zero. Ignore on read.'), _Field('P', 'dFolder', default=0, help='Used in memory only. Write zero. Ignore on read.'), # Dimensioning info. [0] == rows, [1] == cols etc @@ -222,197 +254,386 @@ _Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'), _Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'), _Field('P', 'sIndices', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('f', 'wData', help='The start of the array of data. Must be 64 bit aligned.', count=1), ]) -# End IGOR constants and typedefs from IgorBin.h -def _version_structs(version, byte_order): - if version == 1: - bin = BinHeader1 - wave = WaveHeader2 - elif version == 2: - bin = BinHeader2 - wave = WaveHeader2 - elif version == 3: - bin = BinHeader3 - wave = WaveHeader2 - elif version == 5: - bin = BinHeader5 - wave = WaveHeader5 - else: - raise ValueError( - ('This does not appear to be a valid Igor binary wave file. ' - 'The version field = {}.\n').format(version)) - checkSumSize = bin.size + wave.size - if version == 5: - checkSumSize -= 4 # Version 5 checksum does not include the wData field. - bin.set_byte_order(byte_order) - wave.set_byte_order(byte_order) - return (bin, wave, checkSumSize) - -# Translated from ReadWave() -def load(filename, strict=True): - if hasattr(filename, 'read'): - f = filename # filename is actually a stream object - else: - f = open(filename, 'rb') - try: - BinHeaderCommon.set_byte_order('=') - b = buffer(f.read(BinHeaderCommon.size)) - version = BinHeaderCommon.unpack_from(b)['version'] - needToReorderBytes = _need_to_reorder_bytes(version) - byteOrder = _byte_order(needToReorderBytes) - - if needToReorderBytes: - BinHeaderCommon.set_byte_order(byteOrder) - version = BinHeaderCommon.unpack_from(b)['version'] - bin_struct,wave_struct,checkSumSize = _version_structs( - version, byteOrder) - - b = buffer(b + f.read(bin_struct.size + wave_struct.size - BinHeaderCommon.size)) - c = _checksum(b, byteOrder, 0, checkSumSize) - if c != 0: - raise ValueError( - ('This does not appear to be a valid Igor binary wave file. ' - 'Error in checksum: should be 0, is {}.').format(c)) - bin_info = bin_struct.unpack_from(b) - wave_info = wave_struct.unpack_from(b, offset=bin_struct.size) - if version in [1,2,3]: - tail = 16 # 16 = size of wData field in WaveHeader2 structure - waveDataSize = bin_info['wfmSize'] - wave_struct.size - # = bin_info['wfmSize']-16 - (wave_struct.size - tail) - else: - assert version == 5, version - tail = 4 # 4 = size of wData field in WaveHeader5 structure - waveDataSize = bin_info['wfmSize'] - (wave_struct.size - tail) +class DynamicWaveDataField1 (_DynamicField): + def pre_pack(self, parents, data): + raise NotImplementedError() + + def pre_unpack(self, parents, data): + full_structure = parents[0] + wave_structure = parents[-1] + wave_header_structure = wave_structure.fields[1].format + wave_data = self._get_structure_data(parents, data, wave_structure) + version = data['version'] + bin_header = wave_data['bin_header'] + wave_header = wave_data['wave_header'] + + self.count = wave_header['npnts'] + self.data_size = self._get_size(bin_header, wave_header_structure.size) + + type_ = TYPE_TABLE.get(wave_header['type'], None) + if type_: + self.shape = self._get_shape(bin_header, wave_header) + else: # text wave + type_ = _numpy.dtype('S1') + self.shape = (self.data_size,) # dtype() wrapping to avoid numpy.generic and # getset_descriptor issues with the builtin numpy types # (e.g. int32). It has no effect on our local complex # integers. - if version == 5: - shape = [n for n in wave_info['nDim'] if n > 0] or (0,) + 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: - shape = (wave_info['npnts'],) - t = _numpy.dtype(_numpy.int8) # setup a safe default - if wave_info['type'] == 0: # text wave - shape = (waveDataSize,) - elif wave_info['type'] in TYPE_TABLE or wave_info['npnts']: - t = _numpy.dtype(TYPE_TABLE[wave_info['type']]) - assert waveDataSize == wave_info['npnts'] * t.itemsize, ( - '{}, {}, {}, {}'.format( - waveDataSize, wave_info['npnts'], t.itemsize, t)) + 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: - pass # formula waves - if wave_info['npnts'] == 0: - data_b = buffer('') + 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 = ''.join(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] + # split null-delimited strings + labels = dim_data.split(chr(0)) + 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(''.join(chars)) + start = offset + elif offset == start: + strings.append('') + 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)) else: - tail_data = _array.array('f', b[-tail:]) - data_b = buffer(buffer(tail_data) + f.read(waveDataSize-tail)) - data = _numpy.ndarray( - shape=shape, - dtype=t.newbyteorder(byteOrder), - buffer=data_b, - order='F', - ) + need_to_reorder_bytes = False + old_format = wave_structure.fields[-1].format if version == 1: - pass # No post-data information + wave_structure.fields[-1].format = Wave1 elif version == 2: - # Post-data info: - # * 16 bytes of padding - # * Optional wave note data - pad_b = buffer(f.read(16)) # skip the padding - _assert_null(pad_b, strict=strict) - bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() + wave_structure.fields[-1].format = Wave2 elif version == 3: - # Post-data info: - # * 16 bytes of padding - # * Optional wave note data - # * 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. - """ - pad_b = buffer(f.read(16)) # skip the padding - _assert_null(pad_b, strict=strict) - bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() - bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip() + wave_structure.fields[-1].format = Wave3 elif version == 5: - # Post-data info: - # * Optional wave dependency formula - # * Optional wave note data - # * Optional extended data units data - # * Optional extended dimension units data - # * Optional dimension label data - # * String indices used for text waves only - """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. - - 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. - """ - bin_info['formula'] = str(f.read(bin_info['formulaSize'])).strip() - bin_info['note'] = str(f.read(bin_info['noteSize'])).strip() - bin_info['dataEUnits'] = str(f.read(bin_info['dataEUnitsSize'])).strip() - bin_info['dimEUnits'] = [ - str(f.read(size)).strip() for size in bin_info['dimEUnitsSize']] - bin_info['dimLabels'] = [] - for size in bin_info['dimLabelsSize']: - labels = str(f.read(size)).split(chr(0)) # split null-delimited strings - bin_info['dimLabels'].append([L for L in labels if len(L) > 0]) - if wave_info['type'] == 0: # text wave - sIndices_data = f.read(bin_info['sIndicesSize']) - sIndices_count = bin_info['sIndicesSize']/4 - bin_info['sIndices'] = _numpy.ndarray( - shape=(sIndices_count,), - dtype=_numpy.dtype( - _numpy.uint32()).newbyteorder(byteOrder), - buffer=sIndices_data, - order='F', - ) - - if wave_info['type'] == 0: # text wave - # use sIndices to split data into strings - strings = [] - start = 0 - for i,offset in enumerate(bin_info['sIndices']): - if offset > start: - string = data[start:offset] - strings.append(''.join(chr(x) for x in string)) - start = offset - elif offset == start: - strings.append('') - else: - raise ValueError((offset, bin_info['sIndices'])) - data = _numpy.array(strings) - shape = [n for n in wave_info['nDim'] if n > 0] or (0,) - data.reshape(shape) + wave_structure.fields[-1].format = Wave5 + elif not need_to_reorder_bytes: + raise ValueError( + 'invalid binary wave version: {}'.format(version)) + + if wave_structure.fields[-1].format != old_format: + _LOG.debug('change wave headers from {} to {}'.format( + old_format, wave_structure.fields[-1].format)) + wave_structure.setup() + elif need_to_reorder_bytes: + wave_structure.setup() + + # we might need to unpack again with the new byte order + return need_to_reorder_bytes + + +class DynamicWaveField (_DynamicField): + def post_unpack(self, parents, data): + return + raise NotImplementedError() # TODO + checksum_size = bin.size + wave.size + wave_structure = parents[-1] + if version == 5: + # Version 5 checksum does not include the wData field. + checksum_size -= 4 + c = _checksum(b, parents[-1].byte_order, 0, checksum_size) + if c != 0: + raise ValueError( + ('This does not appear to be a valid Igor binary wave file. ' + 'Error in checksum: should be 0, is {}.').format(c)) + +Wave1 = _DynamicStructure( + name='Wave1', + fields=[ + _Field(BinHeader1, 'bin_header', help='Binary wave header'), + _Field(WaveHeader2, 'wave_header', help='Wave header'), + DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0), + ]) + +Wave2 = _DynamicStructure( + name='Wave2', + fields=[ + _Field(BinHeader2, 'bin_header', help='Binary wave header'), + _Field(WaveHeader2, 'wave_header', help='Wave header'), + DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0), + _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16), + DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0), + ]) + +Wave3 = _DynamicStructure( + name='Wave3', + fields=[ + _Field(BinHeader3, 'bin_header', help='Binary wave header'), + _Field(WaveHeader2, 'wave_header', help='Wave header'), + DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0), + _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16), + DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0), + DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula', count=0), + ]) + +Wave5 = _DynamicStructure( + name='Wave5', + fields=[ + _Field(BinHeader5, 'bin_header', help='Binary wave header'), + _Field(WaveHeader5, 'wave_header', help='Wave header'), + DynamicWaveDataField5('f', 'wData', help='The start of the array of waveform data.', count=0), + DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula.', count=0), + DynamicWaveNoteField('c', 'note', help='Optional wave note data.', count=0), + DynamicDataUnitsField('c', 'data_units', help='Optional extended data units data.', count=0), + DynamicDimensionUnitsField('c', 'dimension_units', help='Optional dimension label data', count=0), + DynamicLabelsField('c', 'labels', help="Optional dimension label data", count=0), + DynamicStringIndicesDataField('P', 'sIndices', help='Dynamic string indices for text waves.', count=0), + ]) + +Wave = _DynamicStructure( + name='Wave', + fields=[ + DynamicVersionField('h', 'version', help='Version number for backwards compatibility.'), + DynamicWaveField(Wave1, 'wave', help='The rest of the wave data.'), + ]) + + +def load(filename): + if hasattr(filename, 'read'): + f = filename # filename is actually a stream object + else: + f = open(filename, 'rb') + try: + Wave.byte_order = '=' + Wave.setup() + data = Wave.unpack_stream(f) finally: if not hasattr(filename, 'read'): f.close() - return data, bin_info, wave_info + return data def save(filename): diff --git a/igor/packed.py b/igor/packed.py index baad25e..564725a 100644 --- a/igor/packed.py +++ b/igor/packed.py @@ -2,6 +2,7 @@ "Read IGOR Packed Experiment files files into records." +from . import LOG as _LOG from .struct import Structure as _Structure from .struct import Field as _Field from .util import byte_order as _byte_order @@ -39,6 +40,7 @@ def load(filename, strict=True, ignore_unknown=True): + _LOG.debug('loading a packed experiment file from {}'.format(filename)) records = [] if hasattr(filename, 'read'): f = filename # filename is actually a stream object @@ -48,26 +50,38 @@ def load(filename, strict=True, ignore_unknown=True): initial_byte_order = '=' try: while True: + PackedFileRecordHeader.byte_order = initial_byte_order + PackedFileRecordHeader.setup() b = buffer(f.read(PackedFileRecordHeader.size)) if not b: break - PackedFileRecordHeader.set_byte_order(initial_byte_order) + _LOG.debug('reading a new packed experiment file record') header = PackedFileRecordHeader.unpack_from(b) if header['version'] and not byte_order: need_to_reorder = _need_to_reorder_bytes(header['version']) byte_order = initial_byte_order = _byte_order(need_to_reorder) + _LOG.debug( + 'get byte order from version: {} (reorder? {})'.format( + byte_order, need_to_reorder)) if need_to_reorder: - PackedFileRecordHeader.set_byte_order(byte_order) + PackedFileRecordHeader.byte_order = byte_order + PackedFileRecordHeader.setup() header = PackedFileRecordHeader.unpack_from(b) + _LOG.debug( + 'reordered version: {}'.format(header['version'])) data = buffer(f.read(header['numDataBytes'])) record_type = _RECORD_TYPE.get( header['recordType'] & PACKEDRECTYPE_MASK, _UnknownRecord) + _LOG.debug('the new record has type {} ({}).'.format( + record_type, header['recordType'])) if record_type in [_UnknownRecord, _UnusedRecord ] and not ignore_unknown: raise KeyError('unkown record type {}'.format( header['recordType'])) records.append(record_type(header, data, byte_order=byte_order)) finally: + _LOG.debug('finished loading {} records from {}'.format( + len(records), filename)) if not hasattr(filename, 'read'): f.close() @@ -122,15 +136,19 @@ def _build_filesystem(records): dir_stack.pop() elif isinstance(record, (_VariablesRecord, _WaveRecord)): if isinstance(record, _VariablesRecord): - _add_variables(dir_stack, cwd, record) - # start with an invalid character to avoid collisions - # with folder names - #filename = ':variables' - #_check_filename(dir_stack, filename) - #cwd[filename] = record + sys_vars = record.variables['variables']['sysVars'].keys() + for filename,value in record.namespace.items(): + if len(dir_stack) > 1 and filename in sys_vars: + # From PTN003: + """When reading a packed file, any system + variables encountered while the current data + folder is not the root should be ignored. + """ + continue + _check_filename(dir_stack, filename) + cwd[filename] = value else: # WaveRecord - filename = ''.join(c for c in record.wave_info['bname'] - ).split('\x00', 1)[0] + filename = record.wave['wave']['wave_header']['bname'] _check_filename(dir_stack, filename) cwd[filename] = record return filesystem @@ -140,22 +158,3 @@ def _check_filename(dir_stack, filename): if filename in cwd: raise ValueError('collision on name {} in {}'.format( filename, ':'.join(d for d,cwd in dir_stack))) - -def _add_variables(dir_stack, cwd, record): - if len(dir_stack) == 1: - # From PTN003: - """When reading a packed file, any system variables - encountered while the current data folder is not the root - should be ignored. - """ - for i,value in enumerate(record.variables['sysVars']): - name = 'K{}'.format(i) - _check_filename(dir_stack, name) - cwd[name] = value - for name,value in ( - record.variables['userVars'].items() + - record.variables['userStrs'].items()): - _check_filename(dir_stack, name) - cwd[name] = value - if record.variables['header']['version'] == 2: - raise NotImplementedError('add dependent variables to filesystem') diff --git a/igor/record/variables.py b/igor/record/variables.py index e55d800..d604bfd 100644 --- a/igor/record/variables.py +++ b/igor/record/variables.py @@ -1,34 +1,135 @@ # Copyright +import io as _io + +from .. import LOG as _LOG from ..binarywave import TYPE_TABLE as _TYPE_TABLE +from ..binarywave import NullStaticStringField as _NullStaticStringField +from ..binarywave import DynamicStringField as _DynamicStringField from ..struct import Structure as _Structure +from ..struct import DynamicStructure as _DynamicStructure from ..struct import Field as _Field +from ..struct import DynamicField as _DynamicField from ..util import byte_order as _byte_order from ..util import need_to_reorder_bytes as _need_to_reorder_bytes from .base import Record -VarHeaderCommon = _Structure( - name='VarHeaderCommon', - fields=[ - _Field('h', 'version', help='Version number for this header.'), - ]) +class ListedStaticStringField (_NullStaticStringField): + """Handle string conversions for multi-count dynamic parents. + + If a field belongs to a multi-count dynamic parent, the parent is + called multiple times to parse each count, and the field's + post-unpack hook gets called after the field is unpacked during + each iteration. This requires alternative logic for getting and + setting the string data. The actual string formatting code is not + affected. + """ + def post_unpack(self, parents, data): + parent_structure = parents[-1] + parent_data = self._get_structure_data(parents, data, parent_structure) + d = self._normalize_string(parent_data[-1][self.name]) + parent_data[-1][self.name] = d + + +class ListedStaticStringField (_NullStaticStringField): + """Handle string conversions for multi-count dynamic parents. + + If a field belongs to a multi-count dynamic parent, the parent is + called multiple times to parse each count, and the field's + post-unpack hook gets called after the field is unpacked during + each iteration. This requires alternative logic for getting and + setting the string data. The actual string formatting code is not + affected. + """ + def post_unpack(self, parents, data): + parent_structure = parents[-1] + parent_data = self._get_structure_data(parents, data, parent_structure) + d = self._normalize_string(parent_data[-1][self.name]) + parent_data[-1][self.name] = d + + +class ListedDynamicStrDataField (_DynamicStringField, ListedStaticStringField): + _size_field = 'strLen' + _null_terminated = False + + def _get_size_data(self, parents, data): + parent_structure = parents[-1] + parent_data = self._get_structure_data(parents, data, parent_structure) + return parent_data[-1][self._size_field] + + +class DynamicVarDataField (_DynamicField): + def pre_pack(self, parents, data): + raise NotImplementedError() + + def post_unpack(self, parents, data): + var_structure = parents[-1] + var_data = self._get_structure_data(parents, data, var_structure) + data = var_data[self.name] + d = {} + for i,value in enumerate(data): + key,value = self._normalize_item(i, value) + d[key] = value + var_data[self.name] = d + + def _normalize_item(self, index, value): + raise NotImplementedError() + + +class DynamicSysVarField (DynamicVarDataField): + def _normalize_item(self, index, value): + name = 'K{}'.format(index) + return (name, value) + + +class DynamicUserVarField (DynamicVarDataField): + def _normalize_item(self, index, value): + name = value['name'] + value = value['num'] + return (name, value) + + +class DynamicUserStrField (DynamicVarDataField): + def _normalize_item(self, index, value): + name = value['name'] + value = value['data'] + return (name, value) + + +class DynamicVarNumField (_DynamicField): + def post_unpack(self, parents, data): + parent_structure = parents[-1] + parent_data = self._get_structure_data(parents, data, parent_structure) + d = self._normalize_numeric_variable(parent_data[-1][self.name]) + parent_data[-1][self.name] = d + + def _normalize_numeric_variable(self, num_var): + t = _TYPE_TABLE[num_var['numType']] + if num_var['numType'] % 2: # complex number + return t(complex(num_var['realPart'], num_var['imagPart'])) + else: + return t(num_var['realPart']) + + +class DynamicFormulaField (_DynamicStringField): + _size_field = 'formulaLen' + _null_terminated = True + # From Variables.h -VarHeader1 = _Structure( +VarHeader1 = _Structure( # `version` field pulled out into VariablesRecord name='VarHeader1', fields=[ - _Field('h', 'version', help='Version number is 1 for this header.'), _Field('h', 'numSysVars', help='Number of system variables (K0, K1, ...).'), _Field('h', 'numUserVars', help='Number of user numeric variables -- may be zero.'), _Field('h', 'numUserStrs', help='Number of user string variables -- may be zero.'), ]) # From Variables.h -VarHeader2 = _Structure( +VarHeader2 = _Structure( # `version` field pulled out into VariablesRecord name='VarHeader2', fields=[ - _Field('h', 'version', help='Version number is 2 for this header.'), _Field('h', 'numSysVars', help='Number of system variables (K0, K1, ...).'), _Field('h', 'numUserVars', help='Number of user numeric variables -- may be zero.'), _Field('h', 'numUserStrs', help='Number of user string variables -- may be zero.'), @@ -37,19 +138,19 @@ ]) # From Variables.h -UserStrVarRec1 = _Structure( +UserStrVarRec1 = _DynamicStructure( name='UserStrVarRec1', fields=[ - _Field('c', 'name', help='Name of the string variable.', count=32), + ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32), _Field('h', 'strLen', help='The real size of the following array.'), - _Field('c', 'data'), + ListedDynamicStrDataField('c', 'data'), ]) # From Variables.h -UserStrVarRec2 = _Structure( +UserStrVarRec2 = _DynamicStructure( name='UserStrVarRec2', fields=[ - _Field('c', 'name', help='Name of the string variable.', count=32), + ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32), _Field('l', 'strLen', help='The real size of the following array.'), _Field('c', 'data'), ]) @@ -65,146 +166,134 @@ ]) # From Variables.h -UserNumVarRec = _Structure( +UserNumVarRec = _DynamicStructure( name='UserNumVarRec', fields=[ - _Field('c', 'name', help='Name of the string variable.', count=32), + ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32), _Field('h', 'type', help='0 = string, 1 = numeric.'), - _Field(VarNumRec, 'num', help='Type and value of the variable if it is numeric. Not used for string.'), + DynamicVarNumField(VarNumRec, 'num', help='Type and value of the variable if it is numeric. Not used for string.'), ]) # From Variables.h -UserDependentVarRec = _Structure( +UserDependentVarRec = _DynamicStructure( name='UserDependentVarRec', fields=[ - _Field('c', 'name', help='Name of the string variable.', count=32), + ListedStaticStringField('c', 'name', help='Name of the string variable.', count=32), _Field('h', 'type', help='0 = string, 1 = numeric.'), _Field(VarNumRec, 'num', help='Type and value of the variable if it is numeric. Not used for string.'), _Field('h', 'formulaLen', help='The length of the dependency formula.'), - _Field('c', 'formula', help='Start of the dependency formula. A C string including null terminator.'), + DynamicFormulaField('c', 'formula', help='Start of the dependency formula. A C string including null terminator.'), ]) -class VariablesRecord (Record): - def __init__(self, *args, **kwargs): - super(VariablesRecord, self).__init__(*args, **kwargs) - # self.header['version'] # record version always 0? - version = self._set_byte_order_and_get_version() - self.structure = self._get_structure(version) - self.variables = self.structure.unpack_from(self.data) - self.variables.update(self._unpack_variable_length_structures(version)) - self._normalize_variables() - - def _set_byte_order_and_get_version(self): - if self.byte_order: - VarHeaderCommon.set_byte_order(self.byte_order) - else: - VarHeaderCommon.set_byte_order('=') - version = VarHeaderCommon.unpack_from(self.data)['version'] - if not self.byte_order: - need_to_reorder = _need_to_reorder_bytes(version) - self.byte_order = _byte_order(need_to_reorder) - if need_to_reorder: - VarHeaderCommon.set_byte_order(self.byte_order) - version = VarHeaderCommon.unpack_from(self.data)['version'] - return version - - def _get_structure(self, version): - if version == 1: - header_struct = VarHeader1 - elif version == 2: - header_struct = VarHeader2 +class DynamicVarHeaderField (_DynamicField): + def pre_pack(self, parents, data): + raise NotImplementedError() + + def post_unpack(self, parents, data): + var_structure = parents[-1] + var_data = self._get_structure_data( + parents, data, var_structure) + var_header_structure = self.format + data = var_data['var_header'] + sys_vars_field = var_structure.get_field('sysVars') + sys_vars_field.count = data['numSysVars'] + sys_vars_field.setup() + user_vars_field = var_structure.get_field('userVars') + user_vars_field.count = data['numUserVars'] + user_vars_field.setup() + user_strs_field = var_structure.get_field('userStrs') + user_strs_field.count = data['numUserStrs'] + user_strs_field.setup() + if 'numDependentVars' in data: + dependent_vars_field = var_structure.get_field('dependentVars') + dependent_vars_field.count = data['numDependentVars'] + dependent_vars_field.setup() + dependent_strs_field = var_structure.get_field('dependentStrs') + dependent_strs_field.count = data['numDependentStrs'] + dependent_strs_field.setup() + var_structure.setup() + + +Variables1 = _DynamicStructure( + name='Variables1', + fields=[ + DynamicVarHeaderField(VarHeader1, 'var_header', help='Variables header'), + DynamicSysVarField('f', 'sysVars', help='System variables', count=0), + DynamicUserVarField(UserNumVarRec, 'userVars', help='User numeric variables', count=0), + DynamicUserStrField(UserStrVarRec1, 'userStrs', help='User string variables', count=0), + ]) + + +Variables2 = _DynamicStructure( + name='Variables2', + fields=[ + DynamicVarHeaderField(VarHeader2, 'var_header', help='Variables header'), + DynamicSysVarField('f', 'sysVars', help='System variables', count=0), + DynamicUserVarField(UserNumVarRec, 'userVars', help='User numeric variables', count=0), + DynamicUserStrField(UserStrVarRec2, 'userStrs', help='User string variables', count=0), + _Field(UserDependentVarRec, 'dependentVars', help='Dependent numeric variables.', count=0), + _Field(UserDependentVarRec, 'dependentStrs', help='Dependent string variables.', count=0), + ]) + + +class DynamicVersionField (_DynamicField): + def pre_pack(self, parents, byte_order): + raise NotImplementedError() + + def post_unpack(self, parents, data): + variables_structure = parents[-1] + variables_data = self._get_structure_data( + parents, data, variables_structure) + version = variables_data['version'] + if variables_structure.byte_order in '@=': + need_to_reorder_bytes = _need_to_reorder_bytes(version) + variables_structure.byte_order = _byte_order(need_to_reorder_bytes) + _LOG.debug( + 'get byte order from version: {} (reorder? {})'.format( + variables_structure.byte_order, need_to_reorder_bytes)) else: - raise NotImplementedError( - 'Variables record version {}'.format(version)) - header = header_struct.unpack_from(self.data) - fields = [ - _Field(header_struct, 'header', help='VarHeader'), - _Field('f', 'sysVars', help='system variables', - count=header['numSysVars']), - _Field(UserNumVarRec, 'userVars', help='user variables', - count=header['numUserVars']), - ] - return _Structure(name='variables', fields=fields) - - def _unpack_variable_length_structures(self, version): - data = {'userStrs': []} - offset = self.structure.size + need_to_reorder_bytes = False + old_format = variables_structure.fields[-1].format if version == 1: - user_str_var_struct = UserStrVarRec1 + variables_structure.fields[-1].format = Variables1 elif version == 2: - user_str_var_struct = UserStrVarRec2 - else: - raise NotImplementedError( - 'Variables record version {}'.format(version)) - user_str_var_struct.set_byte_order(self.byte_order) - for i in range(self.variables['header']['numUserStrs']): - d = user_str_var_struct.unpack_from(self.data, offset) - offset += user_str_var_struct.size - end = offset + d['strLen'] - 1 # one character already in struct - if d['strLen']: - d['data'] = d['data'] + self.data[offset:end] - else: - d['data'] = '' - offset = end - data['userStrs'].append(d) - - if version == 2: - data.update({'dependentVars': [], 'dependentStrs': []}) - UserDependentVarRec.set_byte_order(self.byte_order) - for i in range(self.variables['header']['numDependentVars']): - d,offset = self._unpack_dependent_variable(offset) - data['dependentVars'].append(d) - for i in range(self.variables['header']['numDependentStrs']): - d,offset = self._unpack_dependent_variable(offset) - data['dependentStrs'].append(d) - - if offset != len(self.data): - raise ValueError('too much data ({} extra bytes)'.format( - len(self.data)-offset)) - return data - - def _unpack_dependent_variable(self, offset): - d = UserDependentVarRec.unpack_from(self.data, offset) - offset += UserDependentVarRec.size - end = offset + d['formulaLen'] - 1 # one character already in struct - if d['formulaLen']: - d['formula'] = d['formula'] + self.data[offset:end] - else: - d['formula'] = '' - offset = end - return (d, offset) - - def _normalize_variables(self): - user_vars = {} - for num_var in self.variables['userVars']: - key,value = self._normalize_user_numeric_variable(num_var) - user_vars[key] = value - self.variables['userVars'] = user_vars - user_strs = {} - for str_var in self.variables['userStrs']: - name = self._normalize_null_terminated_string(str_var['name']) - user_strs[name] = str_var['data'] - if self.variables['header']['version'] == 2: - raise NotImplementedError('normalize dependent variables') - self.variables['userStrs'] = user_strs - - def _normalize_null_terminated_string(self, string): - return string.tostring().split('\x00', 1)[0] - - def _normalize_user_numeric_variable(self, user_num_var): - user_num_var['name'] = self._normalize_null_terminated_string( - user_num_var['name']) - if user_num_var['type']: # numeric - value = self._normalize_numeric_variable(user_num_var['num']) - else: # string - value = None - return (user_num_var['name'], value) + variables_structure.fields[-1].format = Variables2 + elif not need_to_reorder_bytes: + raise ValueError( + 'invalid variables record version: {}'.format(version)) - def _normalize_numeric_variable(self, num_var): - t = _TYPE_TABLE[num_var['numType']] - if num_var['numType'] % 2: # complex number - return t(complex(num_var['realPart'], num_var['imagPart'])) - else: - return t(num_var['realPart']) + if variables_structure.fields[-1].format != old_format: + _LOG.debug('change variables record from {} to {}'.format( + old_format, variables_structure.fields[-1].format)) + variables_structure.setup() + elif need_to_reorder_bytes: + variables_structure.setup() + + # we might need to unpack again with the new byte order + return need_to_reorder_bytes + + +VariablesRecordStructure = _DynamicStructure( + name='VariablesRecord', + fields=[ + DynamicVersionField('h', 'version', help='Version number for this header.'), + _Field(Variables1, 'variables', help='The rest of the variables data.'), + ]) + + +class VariablesRecord (Record): + def __init__(self, *args, **kwargs): + super(VariablesRecord, self).__init__(*args, **kwargs) + # self.header['version'] # record version always 0? + VariablesRecordStructure.byte_order = '=' + VariablesRecordStructure.setup() + stream = _io.BytesIO(bytes(self.data)) + self.variables = VariablesRecordStructure.unpack_stream(stream) + self.namespace = {} + for key,value in self.variables['variables'].items(): + if key not in ['var_header']: + _LOG.debug('update namespace {} with {} for {}'.format( + self.namespace, value, key)) + self.namespace.update(value) diff --git a/igor/record/wave.py b/igor/record/wave.py index 93de8a3..db36cfa 100644 --- a/igor/record/wave.py +++ b/igor/record/wave.py @@ -9,8 +9,7 @@ class WaveRecord (Record): def __init__(self, *args, **kwargs): super(WaveRecord, self).__init__(*args, **kwargs) - self.wave,self.bin_info,self.wave_info = _loadibw( - _BytesIO(bytes(self.data)), strict=False) + self.wave = _loadibw(_BytesIO(bytes(self.data))) def __str__(self): return str(self.wave) diff --git a/igor/struct.py b/igor/struct.py index c9886a0..fce93b5 100644 --- a/igor/struct.py +++ b/igor/struct.py @@ -9,10 +9,15 @@ """ from __future__ import absolute_import +import io as _io +import logging as _logging +import pprint as _pprint import struct as _struct import numpy as _numpy +from . import LOG as _LOG + _buffer = buffer # save builtin buffer for clobbered situations @@ -34,7 +39,7 @@ class Field (object): >>> time = Field( ... 'I', 'time', default=0, help='POSIX time') - >>> time.total_count + >>> time.arg_count 1 >>> list(time.pack_data(1)) [1] @@ -49,7 +54,7 @@ class Field (object): >>> data = Field( ... 'f', 'data', help='example data', count=(2,3,4)) - >>> data.total_count + >>> data.arg_count 24 >>> list(data.indexes()) # doctest: +ELLIPSIS [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 1, 0], ..., [1, 2, 3]] @@ -60,7 +65,7 @@ class Field (object): [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ..., 19, 20, 21, 22, 23] >>> list(data.pack_item(3)) [3] - >>> data.unpack_data(range(data.total_count)) + >>> data.unpack_data(range(data.arg_count)) array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]], @@ -75,10 +80,10 @@ class Field (object): >>> run = Structure('run', fields=[time, data]) >>> runs = Field(run, 'runs', help='pair of runs', count=2) - >>> runs.total_count # = 2 * (1 + 24) + >>> runs.arg_count # = 2 * (1 + 24) 50 - >>> data1 = numpy.arange(data.total_count).reshape(data.count) - >>> data2 = data1 + data.total_count + >>> data1 = numpy.arange(data.arg_count).reshape(data.count) + >>> data2 = data1 + data.arg_count >>> list(runs.pack_data( ... [{'time': 100, 'data': data1}, ... {'time': 101, 'data': data2}]) @@ -87,7 +92,7 @@ class Field (object): >>> list(runs.pack_item({'time': 100, 'data': data1}) ... ) # doctest: +ELLIPSIS [100, 0, 1, 2, ..., 22, 23] - >>> pprint(runs.unpack_data(range(runs.total_count))) + >>> pprint(runs.unpack_data(range(runs.arg_count))) [{'data': array([[[ 1, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12]], @@ -137,12 +142,24 @@ def __init__(self, format, name, default=None, help=None, count=1): self.default = default self.help = help self.count = count - self.item_count = _numpy.prod(count) # number of item repeats + self.setup() + + def setup(self): + """Setup any dynamic properties of a field. + + Use this method to recalculate dynamic properities after + changing the basic properties set during initialization. + """ + _LOG.debug('setup {}'.format(self)) + self.item_count = _numpy.prod(self.count) # number of item repeats if isinstance(self.format, Structure): - self.structure_count = sum(f.total_count for f in format.fields) - self.total_count = self.item_count * self.structure_count + self.structure_count = sum( + f.arg_count for f in self.format.fields) + self.arg_count = self.item_count * self.structure_count + elif self.format == 'x': + self.arg_count = 0 # no data in padding bytes else: - self.total_count = self.item_count # struct.Struct format chars + self.arg_count = self.item_count # struct.Struct format args def __str__(self): return self.__repr__() @@ -220,9 +237,10 @@ def pack_item(self, item=None): def unpack_data(self, data): """Inverse of .pack_data""" + _LOG.debug('unpack {} for {} {}'.format(data, self, self.format)) iterator = iter(data) try: - items = [iterator.next() for i in range(self.total_count)] + items = [iterator.next() for i in range(self.arg_count)] except StopIteration: raise ValueError('not enough data to unpack {}'.format(self)) try: @@ -238,7 +256,11 @@ def unpack_data(self, data): else: items = [[i] for i in items] unpacked = [self.unpack_item(i) for i in items] - if self.count == 1: + if self.arg_count: + count = self.count + else: + count = 0 # padding bytes, etc. + if count == 1: return unpacked[0] if isinstance(self.format, Structure): try: @@ -249,10 +271,12 @@ def unpack_data(self, data): raise NotImplementedError('reshape Structure field') else: unpacked = _numpy.array(unpacked) - unpacked = unpacked.reshape(self.count) + _LOG.debug('reshape {} data from {} to {}'.format( + self, unpacked.shape, count)) + unpacked = unpacked.reshape(count) return unpacked - def unpack_item(self, item): + def unpack_item(self, item): """Inverse of .unpack_item""" if isinstance(self.format, Structure): return self.format._unpack_item(item) @@ -261,6 +285,54 @@ def unpack_item(self, item): return item[0] +class DynamicField (Field): + """Represent a DynamicStructure field with a dynamic definition. + + Adds the methods ``.pre_pack``, ``pre_unpack``, and + ``post_unpack``, all of which are called when a ``DynamicField`` + is used by a ``DynamicStructure``. Each method takes the + arguments ``(parents, data)``, where ``parents`` is a list of + ``DynamicStructure``\s that own the field and ``data`` is a dict + hierarchy of the structure data. + + See the ``DynamicStructure`` docstring for the exact timing of the + method calls. + + See Also + -------- + Field, DynamicStructure + """ + def pre_pack(self, parents, data): + "Prepare to pack." + pass + + def pre_unpack(self, parents, data): + "React to previously unpacked data" + pass + + def post_unpack(self, parents, data): + "React to our own data" + pass + + def _get_structure_data(self, parents, data, structure): + """Extract the data belonging to a particular ancestor structure. + """ + d = data + s = parents[0] + if s == structure: + return d + for p in parents[1:]: + for f in s.fields: + if f.format == p: + s = p + d = d[f.name] + break + assert s == p, (s, p) + if p == structure: + break + return d + + class Structure (_struct.Struct): r"""Represent a C structure. @@ -289,7 +361,7 @@ class Structure (_struct.Struct): struct run runs[2]; } - As + As: >>> time = Field('I', 'time', default=0, help='POSIX time') >>> data = Field( @@ -303,19 +375,30 @@ class Structure (_struct.Struct): The structures automatically calculate the flattened data format: >>> run.format - '=Ihhhhhh' + '@Ihhhhhh' >>> run.size # 4 + 2*3*2 16 >>> experiment.format - '=HIhhhhhhIhhhhhh' - >>> experiment.size # 2 + 2*(4 + 2*3*2) + '@HIhhhhhhIhhhhhh' + >>> experiment.size # 2 + 2 + 2*(4 + 2*3*2) + 36 + + The first two elements in the above size calculation are 2 (for + the unsigned short, 'H') and 2 (padding so the unsigned int aligns + with a 4-byte block). If you select a byte ordering that doesn't + mess with alignment and recalculate the format, the padding goes + away and you get: + + >>> experiment.set_byte_order('>') + >>> experiment.get_format() + '>HIhhhhhhIhhhhhh' + >>> experiment.size 34 You can read data out of any object supporting the buffer interface: >>> b = array.array('B', range(experiment.size)) - >>> experiment.set_byte_order('>') >>> d = experiment.unpack_from(buffer=b) >>> pprint(d) {'runs': [{'data': array([[1543, 2057, 2571], @@ -375,12 +458,15 @@ class Structure (_struct.Struct): >>> b2 == b True """ - def __init__(self, name, fields, byte_order='='): + _byte_order_symbols = '@=<>!' + + def __init__(self, name, fields, byte_order='@'): # '=' for native byte order, standard size and alignment # See http://docs.python.org/library/struct for details self.name = name self.fields = fields - self.set_byte_order(byte_order) + self.byte_order = byte_order + self.setup() def __str__(self): return self.name @@ -389,25 +475,46 @@ def __repr__(self): return '<{} {} {}>'.format( self.__class__.__name__, self.name, id(self)) + def setup(self): + """Setup any dynamic properties of a structure. + + Use this method to recalculate dynamic properities after + changing the basic properties set during initialization. + """ + _LOG.debug('setup {!r}'.format(self)) + self.set_byte_order(self.byte_order) + self.get_format() + def set_byte_order(self, byte_order): """Allow changing the format byte_order on the fly. """ - if (hasattr(self, 'format') and self.format != None - and self.format.startswith(byte_order)): - return # no need to change anything - format = [] + _LOG.debug('set byte order for {!r} to {}'.format(self, byte_order)) + self.byte_order = byte_order for field in self.fields: if isinstance(field.format, Structure): - field_format = field.format.sub_format( - ) * field.item_count - else: - field_format = [field.format]*field.item_count - format.extend(field_format) - super(Structure, self).__init__( - format=byte_order+''.join(format).replace('P', 'L')) + field.format.set_byte_order(byte_order) + + def get_format(self): + format = self.byte_order + ''.join(self.sub_format()) + # P format only allowed for native byte ordering + # Convert P to I for ILP32 compatibility when running on a LP64. + format = format.replace('P', 'I') + try: + super(Structure, self).__init__(format=format) + except _struct.error as e: + raise ValueError((e, format)) + return format def sub_format(self): - return self.format.lstrip('=<>') # byte order handled by parent + _LOG.debug('calculate sub-format for {!r}'.format(self)) + for field in self.fields: + if isinstance(field.format, Structure): + field_format = list( + field.format.sub_format()) * field.item_count + else: + field_format = [field.format]*field.item_count + for fmt in field_format: + yield fmt def _pack_item(self, item=None): """Linearize a single count of the structure's data to a flat iterable @@ -430,7 +537,7 @@ def _unpack_item(self, args): iterator = iter(args) for f in self.fields: try: - items = [iterator.next() for i in range(f.total_count)] + items = [iterator.next() for i in range(f.arg_count)] except StopIteration: raise ValueError('not enough data to unpack {}.{}'.format( self, f)) @@ -445,7 +552,10 @@ def _unpack_item(self, args): def pack(self, data): args = list(self._pack_item(data)) - return super(Structure, self).pack(*args) + try: + return super(Structure, self).pack(*args) + except: + raise ValueError(self.format) def pack_into(self, buffer, offset=0, data={}): args = list(self._pack_item(data)) @@ -457,20 +567,247 @@ def unpack(self, *args, **kwargs): return self._unpack_item(args) def unpack_from(self, buffer, offset=0, *args, **kwargs): - try: - args = super(Structure, self).unpack_from( - buffer, offset, *args, **kwargs) - except _struct.error as e: - if not self.name in ('WaveHeader2', 'WaveHeader5'): - raise - # HACK! For WaveHeader5, when npnts is 0, wData is - # optional. If we couldn't unpack the structure, fill in - # wData with zeros and try again, asserting that npnts is - # zero. - if len(buffer) - offset < self.size: - # missing wData? Pad with zeros - buffer += _buffer('\x00'*(self.size + offset - len(buffer))) - args = super(Structure, self).unpack_from(buffer, offset) - data = self._unpack_item(args) - assert data['npnts'] == 0, data['npnts'] + _LOG.debug( + 'unpack {!r} for {!r} ({}, offset={}) with {} ({})'.format( + buffer, self, len(buffer), offset, self.format, self.size)) + args = super(Structure, self).unpack_from( + buffer, offset, *args, **kwargs) + return self._unpack_item(args) + + def get_field(self, name): + return [f for f in self.fields if f.name == name][0] + + +class DebuggingStream (object): + def __init__(self, stream): + self.stream = stream + + def read(self, size): + data = self.stream.read(size) + _LOG.debug('read {} from {}: ({}) {!r}'.format( + size, self.stream, len(data), data)) + return data + + +class DynamicStructure (Structure): + r"""Represent a C structure field with a dynamic definition. + + Any dynamic fields have their ``.pre_pack`` called before any + structure packing is done. ``.pre_unpack`` is called for a + particular field just before that field's ``.unpack_data`` call. + ``.post_unpack`` is called for a particular field just after + ``.unpack_data``. If ``.post_unpack`` returns ``True``, the same + field is unpacked again. + + Examples + -------- + + >>> from pprint import pprint + + This allows you to define structures where some portion of the + global structure depends on earlier data. For example, in the + quasi-C structure:: + + struct vector { + unsigned int length; + short data[length]; + } + + You can generate a Python version of this structure in two ways, + with a dynamic ``length``, or with a dynamic ``data``. In both + cases, the required methods are the same, the only difference is + where you attach them. + + >>> def packer(self, parents, data): + ... vector_structure = parents[-1] + ... vector_data = self._get_structure_data( + ... parents, data, vector_structure) + ... length = len(vector_data['data']) + ... vector_data['length'] = length + ... data_field = vector_structure.get_field('data') + ... data_field.count = length + ... data_field.setup() + >>> def unpacker(self, parents, data): + ... vector_structure = parents[-1] + ... vector_data = self._get_structure_data( + ... parents, data, vector_structure) + ... length = vector_data['length'] + ... data_field = vector_structure.get_field('data') + ... data_field.count = length + ... data_field.setup() + + >>> class DynamicLengthField (DynamicField): + ... def pre_pack(self, parents, data): + ... packer(self, parents, data) + ... def post_unpack(self, parents, data): + ... unpacker(self, parents, data) + >>> dynamic_length_vector = DynamicStructure('vector', + ... fields=[ + ... DynamicLengthField('I', 'length'), + ... Field('h', 'data', count=0), + ... ], + ... byte_order='>') + >>> class DynamicDataField (DynamicField): + ... def pre_pack(self, parents, data): + ... packer(self, parents, data) + ... def pre_unpack(self, parents, data): + ... unpacker(self, parents, data) + >>> dynamic_data_vector = DynamicStructure('vector', + ... fields=[ + ... Field('I', 'length'), + ... DynamicDataField('h', 'data', count=0), + ... ], + ... byte_order='>') + + >>> b = '\x00\x00\x00\x02\x01\x02\x03\x04' + >>> d = dynamic_length_vector.unpack(b) + >>> pprint(d) + {'data': array([258, 772]), 'length': 2} + >>> d = dynamic_data_vector.unpack(b) + >>> pprint(d) + {'data': array([258, 772]), 'length': 2} + + >>> d['data'] = [1,2,3,4] + >>> dynamic_length_vector.pack(d) + '\x00\x00\x00\x04\x00\x01\x00\x02\x00\x03\x00\x04' + >>> dynamic_data_vector.pack(d) + '\x00\x00\x00\x04\x00\x01\x00\x02\x00\x03\x00\x04' + + The implementation is a good deal more complicated than the one + for ``Structure``, because we must make multiple calls to + ``struct.Struct.unpack`` to unpack the data. + """ + #def __init__(self, *args, **kwargs): + # pass #self.parent = .. + + def _pre_pack(self, parents=None, data=None): + if parents is None: + parents = [self] + else: + parents = parents + [self] + for f in self.fields: + if hasattr(f, 'pre_pack'): + _LOG.debug('pre-pack {}'.format(f)) + f.pre_pack(parents=parents, data=data) + if isinstance(f.format, DynamicStructure): + _LOG.debug('pre-pack {!r}'.format(f.format)) + f._pre_pack(parents=parents, data=data) + + def pack(self, data): + self._pre_pack(data=data) + self.setup() + return super(DynamicStructure, self).pack(data) + + def pack_into(self, buffer, offset=0, data={}): + self._pre_pack(data=data) + self.setup() + return super(DynamicStructure, self).pack_into( + buffer=buffer, offset=offset, data=data) + + def unpack_stream(self, stream, parents=None, data=None, d=None): + # `d` is the working data directory + if data is None: + parents = [self] + data = d = {} + if _LOG.level == _logging.DEBUG: + stream = DebuggingStream(stream) + else: + parents = parents + [self] + + for f in self.fields: + _LOG.debug('parsing {!r}.{} (count={}, item_count={})'.format( + self, f, f.count, f.item_count)) + _LOG.debug('data:\n{}'.format(_pprint.pformat(data))) + if hasattr(f, 'pre_unpack'): + _LOG.debug('pre-unpack {}'.format(f)) + f.pre_unpack(parents=parents, data=data) + + if hasattr(f, 'unpack'): # override default unpacking + _LOG.debug('override unpack for {}'.format(f)) + d[f.name] = f.unpack(stream) + continue + + # setup for unpacking loop + if isinstance(f.format, Structure): + f.format.set_byte_order(self.byte_order) + f.setup() + f.format.setup() + if isinstance(f.format, DynamicStructure): + if f.item_count == 1: + # TODO, fix in case we *want* an array + d[f.name] = {} + f.format.unpack_stream( + stream, parents=parents, data=data, d=d[f.name]) + else: + d[f.name] = [] + for i in range(f.item_count): + x = {} + d[f.name].append(x) + f.format.unpack_stream( + stream, parents=parents, data=data, d=x) + if hasattr(f, 'post_unpack'): + _LOG.debug('post-unpack {}'.format(f)) + repeat = f.post_unpack(parents=parents, data=data) + if repeat: + raise NotImplementedError( + 'cannot repeat unpack for dynamic structures') + continue + if isinstance(f.format, Structure): + _LOG.debug('parsing {} bytes for {}'.format( + f.format.size, f.format.format)) + bs = [stream.read(f.format.size) for i in range(f.item_count)] + def unpack(): + f.format.set_byte_order(self.byte_order) + f.setup() + f.format.setup() + x = [f.format.unpack_from(b) for b in bs] + if len(x) == 1: # TODO, fix in case we *want* an array + x = x[0] + return x + else: + field_format = self.byte_order + f.format*f.item_count + field_format = field_format.replace('P', 'I') + try: + size = _struct.calcsize(field_format) + except _struct.error as e: + _LOG.error(e) + _LOG.error('{}.{}: {}'.format(self, f, field_format)) + raise + _LOG.debug('parsing {} bytes for preliminary {}'.format( + size, field_format)) + raw = stream.read(size) + if len(raw) < size: + raise ValueError( + 'not enough data to unpack {}.{} ({} < {})'.format( + self, f, len(raw), size)) + def unpack(): + field_format = self.byte_order + f.format*f.item_count + field_format = field_format.replace('P', 'I') + _LOG.debug('parse previous bytes using {}'.format( + field_format)) + struct = _struct.Struct(field_format) + items = struct.unpack(raw) + return f.unpack_data(items) + + # unpacking loop + repeat = True + while repeat: + d[f.name] = unpack() + if hasattr(f, 'post_unpack'): + _LOG.debug('post-unpack {}'.format(f)) + repeat = f.post_unpack(parents=parents, data=data) + else: + repeat = False + if repeat: + _LOG.debug('repeat unpack for {}'.format(f)) + + return data + + def unpack(self, string): + stream = _io.BytesIO(string) + return self.unpack_stream(stream) + + def unpack_from(self, buffer, offset=0, *args, **kwargs): + args = super(Structure, self).unpack_from( + buffer, offset, *args, **kwargs) return self._unpack_item(args) diff --git a/test/test.py b/test/test.py index af65c79..1567ff3 100644 --- a/test/test.py +++ b/test/test.py @@ -2,595 +2,571 @@ r"""Test the igor module by loading sample files. ->>> dumpibw('mac-double.ibw', strict=False) # doctest: +REPORT_UDIFF -array([ 5., 4., 3., 2., 1.]) -{'checksum': 25137, - 'note': '', - 'noteSize': 0, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 166} -{'aModified': 0, - 'bname': array(['d', 'o', 'u', 'b', 'l', 'e', '', '', '', '', '', '', '', '', '', - '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 3001587842, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 3001587842, - 'next': 0, - 'npnts': 5, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 4, - 'useBits': '\x00', - 'wData': array([ 2.3125, 0. , 2.25 , 0. ]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')} +>>> dumpibw('mac-double.ibw') # doctest: +REPORT_UDIFF +{'version': 2, + 'wave': {'bin_header': {'checksum': 25137, + 'noteSize': 0, + 'pictSize': 0, + 'wfmSize': 166}, + 'note': '', + 'padding': array([], dtype=float64), + 'wData': array([ 5., 4., 3., 2., 1.]), + 'wave_header': {'aModified': 0, + 'bname': 'double', + 'botFullScale': 0.0, + 'creationDate': 3001587842, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 3001587842, + 'next': 0, + 'npnts': 5, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 4, + 'useBits': '\x00', + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}}} >>> dumpibw('mac-textWave.ibw') # doctest: +REPORT_UDIFF -array(['Mary', 'had', 'a', 'little', 'lamb'], - dtype='|S6') -{'checksum': 5554, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': '', - 'formulaSize': 0, - 'note': '', - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndices': array([ 4, 7, 8, 14, 18], dtype=uint32), - 'sIndicesSize': 20, - 'version': 5, - 'wfmSize': 338} -{'aModified': 0, - 'bname': array(['t', 'e', 'x', 't', '0', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 3001571199, - 'dFolder': 69554896, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 22, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], +{'version': 5, + 'wave': {'bin_header': {'checksum': 5554, + 'dataEUnitsSize': 0, + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formulaSize': 0, + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 20, + 'wfmSize': 338}, + 'data_units': '', + 'dimension_units': '', + 'formula': '', + 'labels': [[], [], [], []], + 'note': '', + 'sIndices': array([ 4, 7, 8, 14, 18]), + 'wData': array(['Mary', 'had', 'a', 'little', 'lamb'], + dtype='|S6'), + 'wave_header': {'aModified': 0, + 'bname': 'text0', + 'botFullScale': 0.0, + 'creationDate': 3001571199, + 'dFolder': 69554896, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 22, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001571215, - 'nDim': array([5, 0, 0, 0]), - 'next': 0, - 'npnts': 5, - 'sIndices': 69557296, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 1, - 'topFullScale': 0.0, - 'type': 0, - 'useBits': '\x00', - 'wData': 236398480.0, - 'wModified': 0, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0} + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001571215, + 'nDim': array([5, 0, 0, 0]), + 'next': 0, + 'npnts': 5, + 'sIndices': 69557296, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 1, + 'topFullScale': 0.0, + 'type': 0, + 'useBits': '\x00', + 'wModified': 0, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}}} ->>> dumpibw('mac-version2.ibw', strict=False) # doctest: +REPORT_UDIFF -array([ 5., 4., 3., 2., 1.], dtype=float32) -{'checksum': -16803, - 'note': 'This is a test.', - 'noteSize': 15, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 146} -{'aModified': 0, - 'bname': array(['v', 'e', 'r', 's', 'i', 'o', 'n', '2', '', '', '', '', '', '', '', - '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 3001251979, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 3001573594, - 'next': 0, - 'npnts': 5, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': array([ 5., 4., 3., 2.]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')} +>>> dumpibw('mac-version2.ibw') # doctest: +REPORT_UDIFF +{'version': 2, + 'wave': {'bin_header': {'checksum': -16803, + 'noteSize': 15, + 'pictSize': 0, + 'wfmSize': 146}, + 'note': 'This is a test.', + 'padding': array([], dtype=float64), + 'wData': array([ 5., 4., 3., 2., 1.], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'version2', + 'botFullScale': 0.0, + 'creationDate': 3001251979, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 3001573594, + 'next': 0, + 'npnts': 5, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}}} ->>> dumpibw('mac-version3Dependent.ibw', strict=False) # doctest: +REPORT_UDIFF -array([], dtype=int8) -{'checksum': 0, - 'formula': '', - 'formulaSize': 0, - 'note': '', - 'noteSize': 8257536, - 'pictSize': 262144, - 'version': 3, - 'wfmSize': 0} -{'aModified': 10, - 'bname': array(['', '', 'v', 'e', 'r', 's', 'i', 'o', 'n', '3', 'D', 'e', 'p', 'e', - 'n', 'd', 'e', 'n', 't', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 1507328, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': -487849984, - 'fileName': 0, - 'formula': 1577, - 'fsValid': 1, - 'hsA': 4.5193417557662e-309, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 45801, - 'next': 131072, - 'npnts': 0, - 'srcFldr': 0, - 'swModified': 1, - 'topFullScale': 0.0, - 'type': -32334, - 'useBits': '\x00', - 'wData': array([ 0., 0., 0., 0.]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 3835494400, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')} +>>> dumpibw('mac-version3Dependent.ibw') # doctest: +REPORT_UDIFF +{'version': 3, + 'wave': {'bin_header': {'checksum': -32334, + 'formulaSize': 4, + 'noteSize': 0, + 'pictSize': 0, + 'wfmSize': 126}, + 'formula': ' K0', + 'note': '', + 'padding': array([], dtype=float64), + 'wData': array([], dtype=float32), + 'wave_header': {'aModified': 3, + 'bname': 'version3Dependent', + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 23, + 'fileName': 0, + 'formula': 103408364, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 3001672861, + 'next': 0, + 'npnts': 10, + 'srcFldr': 0, + 'swModified': 1, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 1, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}}} >>> dumpibw('mac-version5.ibw') # doctest: +REPORT_UDIFF -array([ 5., 4., 3., 2., 1.], dtype=float32) -{'checksum': -12033, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [['Column0'], [], [], []], - 'dimLabelsSize': array([64, 0, 0, 0]), - 'formula': '', - 'formulaSize': 0, - 'note': 'This is a test.', - 'noteSize': 15, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 340} -{'aModified': 0, - 'bname': array(['v', 'e', 'r', 's', 'i', 'o', 'n', '5', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 3001252180, - 'dFolder': 69554896, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 27, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([69554136, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], +{'version': 5, + 'wave': {'bin_header': {'checksum': -12033, + 'dataEUnitsSize': 0, + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabelsSize': array([64, 0, 0, 0]), + 'formulaSize': 0, + 'noteSize': 15, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'wfmSize': 340}, + 'data_units': '', + 'dimension_units': '', + 'formula': '', + 'labels': [['Column0'], [], [], []], + 'note': 'This is a test.', + 'sIndices': array([], dtype=float64), + 'wData': array([ 5., 4., 3., 2., 1.], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'version5', + 'botFullScale': 0.0, + 'creationDate': 3001252180, + 'dFolder': 69554896, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 27, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([69554136, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 69554292, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001573601, - 'nDim': array([5, 0, 0, 0]), - 'next': 69555212, - 'npnts': 5, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': -32349, - 'swModified': 1, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 5.0, - 'wModified': 0, - 'waveNoteH': 69554032, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0} + 'fileName': 69554292, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001573601, + 'nDim': array([5, 0, 0, 0]), + 'next': 69555212, + 'npnts': 5, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': -32349, + 'swModified': 1, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 0, + 'waveNoteH': 69554032, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}}} >>> dumpibw('mac-zeroPointWave.ibw') # doctest: +REPORT_UDIFF -array([], dtype=float32) -{'checksum': -15649, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': '', - 'formulaSize': 0, - 'note': '', - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 320} -{'aModified': 3, - 'bname': array(['z', 'e', 'r', 'o', 'W', 'a', 'v', 'e', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 3001573964, - 'dFolder': 69554896, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 29, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], +{'version': 5, + 'wave': {'bin_header': {'checksum': -15649, + 'dataEUnitsSize': 0, + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formulaSize': 0, + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'wfmSize': 320}, + 'data_units': '', + 'dimension_units': '', + 'formula': '', + 'labels': [[], [], [], []], + 'note': '', + 'sIndices': array([], dtype=float64), + 'wData': array([], dtype=float32), + 'wave_header': {'aModified': 3, + 'bname': 'zeroWave', + 'botFullScale': 0.0, + 'creationDate': 3001573964, + 'dFolder': 69554896, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 29, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001573964, - 'nDim': array([0, 0, 0, 0]), - 'next': 0, - 'npnts': 0, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 1, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 0.0, - 'wModified': 1, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0} + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001573964, + 'nDim': array([0, 0, 0, 0]), + 'next': 0, + 'npnts': 0, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 1, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 1, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}}} >>> dumpibw('win-double.ibw') # doctest: +REPORT_UDIFF -array([ 5., 4., 3., 2., 1.]) -{'checksum': 28962, - 'note': '', - 'noteSize': 0, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 166} -{'aModified': 0, - 'bname': array(['d', 'o', 'u', 'b', 'l', 'e', '', '', '', '', '', '', '', '', '', - '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 3001587842, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 3001587842, - 'next': 0, - 'npnts': 5, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 4, - 'useBits': '\x00', - 'wData': array([ 0. , 2.3125, 0. , 2.25 ]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')} +{'version': 2, + 'wave': {'bin_header': {'checksum': 28962, + 'noteSize': 0, + 'pictSize': 0, + 'wfmSize': 166}, + 'note': '', + 'padding': array([], dtype=float64), + 'wData': array([ 5., 4., 3., 2., 1.]), + 'wave_header': {'aModified': 0, + 'bname': 'double', + 'botFullScale': 0.0, + 'creationDate': 3001587842, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 3001587842, + 'next': 0, + 'npnts': 5, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 4, + 'useBits': '\x00', + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}}} >>> dumpibw('win-textWave.ibw') # doctest: +REPORT_UDIFF -array(['Mary', 'had', 'a', 'little', 'lamb'], - dtype='|S6') -{'checksum': 184, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': '', - 'formulaSize': 0, - 'note': '', - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndices': array([ 4, 7, 8, 14, 18], dtype=uint32), - 'sIndicesSize': 20, - 'version': 5, - 'wfmSize': 338} -{'aModified': 0, - 'bname': array(['t', 'e', 'x', 't', '0', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 3001571199, - 'dFolder': 8108612, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 32, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], +{'version': 5, + 'wave': {'bin_header': {'checksum': 184, + 'dataEUnitsSize': 0, + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formulaSize': 0, + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 20, + 'wfmSize': 338}, + 'data_units': '', + 'dimension_units': '', + 'formula': '', + 'labels': [[], [], [], []], + 'note': '', + 'sIndices': array([ 4, 7, 8, 14, 18]), + 'wData': array(['Mary', 'had', 'a', 'little', 'lamb'], + dtype='|S6'), + 'wave_header': {'aModified': 0, + 'bname': 'text0', + 'botFullScale': 0.0, + 'creationDate': 3001571199, + 'dFolder': 8108612, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 32, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 7814472, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001571215, - 'nDim': array([5, 0, 0, 0]), - 'next': 0, - 'npnts': 5, - 'sIndices': 8133100, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': -1007, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 0, - 'useBits': '\x00', - 'wData': 7.865683337909351e+34, - 'wModified': 1, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0} + 'fileName': 7814472, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001571215, + 'nDim': array([5, 0, 0, 0]), + 'next': 0, + 'npnts': 5, + 'sIndices': 8133100, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': -1007, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 0, + 'useBits': '\x00', + 'wModified': 1, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}}} >>> dumpibw('win-version2.ibw') # doctest: +REPORT_UDIFF -array([ 5., 4., 3., 2., 1.], dtype=float32) -{'checksum': 1047, - 'note': 'This is a test.', - 'noteSize': 15, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 146} -{'aModified': 0, - 'bname': array(['v', 'e', 'r', 's', 'i', 'o', 'n', '2', '', '', '', '', '', '', '', - '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 3001251979, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 3001573594, - 'next': 0, - 'npnts': 5, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': array([ 5., 4., 3., 2.]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')} +{'version': 2, + 'wave': {'bin_header': {'checksum': 1047, + 'noteSize': 15, + 'pictSize': 0, + 'wfmSize': 146}, + 'note': 'This is a test.', + 'padding': array([], dtype=float64), + 'wData': array([ 5., 4., 3., 2., 1.], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'version2', + 'botFullScale': 0.0, + 'creationDate': 3001251979, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 3001573594, + 'next': 0, + 'npnts': 5, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}}} >>> dumpibw('win-version5.ibw') # doctest: +REPORT_UDIFF -array([ 5., 4., 3., 2., 1.], dtype=float32) -{'checksum': 13214, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [['Column0'], [], [], []], - 'dimLabelsSize': array([64, 0, 0, 0]), - 'formula': '', - 'formulaSize': 0, - 'note': 'This is a test.', - 'noteSize': 15, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 340} -{'aModified': 0, - 'bname': array(['v', 'e', 'r', 's', 'i', 'o', 'n', '5', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 3001252180, - 'dFolder': 8108612, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 30, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([8138784, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], +{'version': 5, + 'wave': {'bin_header': {'checksum': 13214, + 'dataEUnitsSize': 0, + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabelsSize': array([64, 0, 0, 0]), + 'formulaSize': 0, + 'noteSize': 15, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'wfmSize': 340}, + 'data_units': '', + 'dimension_units': '', + 'formula': '', + 'labels': [['Column0'], [], [], []], + 'note': 'This is a test.', + 'sIndices': array([], dtype=float64), + 'wData': array([ 5., 4., 3., 2., 1.], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'version5', + 'botFullScale': 0.0, + 'creationDate': 3001252180, + 'dFolder': 8108612, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 30, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([8138784, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 8131824, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001573601, - 'nDim': array([5, 0, 0, 0]), - 'next': 8125236, - 'npnts': 5, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': -1007, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 5.0, - 'wModified': 1, - 'waveNoteH': 8131596, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0} + 'fileName': 8131824, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001573601, + 'nDim': array([5, 0, 0, 0]), + 'next': 8125236, + 'npnts': 5, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': -1007, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 1, + 'waveNoteH': 8131596, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}}} >>> dumpibw('win-zeroPointWave.ibw') # doctest: +REPORT_UDIFF -array([], dtype=float32) -{'checksum': 27541, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': '', - 'formulaSize': 0, - 'note': '', - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 320} -{'aModified': 3, - 'bname': array(['z', 'e', 'r', 'o', 'W', 'a', 'v', 'e', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 3001573964, - 'dFolder': 8108612, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 31, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], +{'version': 5, + 'wave': {'bin_header': {'checksum': 27541, + 'dataEUnitsSize': 0, + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formulaSize': 0, + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'wfmSize': 320}, + 'data_units': '', + 'dimension_units': '', + 'formula': '', + 'labels': [[], [], [], []], + 'note': '', + 'sIndices': array([], dtype=float64), + 'wData': array([], dtype=float32), + 'wave_header': {'aModified': 3, + 'bname': 'zeroWave', + 'botFullScale': 0.0, + 'creationDate': 3001573964, + 'dFolder': 8108612, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 31, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 8125252, - 'formula': 0, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 3001573964, - 'nDim': array([0, 0, 0, 0]), - 'next': 8133140, - 'npnts': 0, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': -1007, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 0.0, - 'wModified': 1, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0} + 'fileName': 8125252, + 'formula': 0, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 3001573964, + 'nDim': array([0, 0, 0, 0]), + 'next': 8133140, + 'npnts': 0, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': -1007, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 1, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}}} >>> dumppxp('polar-graphs-demo.pxp') # doctest: +REPORT_UDIFF, +ELLIPSIS record 0: @@ -654,19 +630,44 @@ record 29: record 30: -{'header': {'numSysVars': 21, - 'numUserStrs': 0, - 'numUserVars': 0, - 'version': 1}, - 'sysVars': array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., - 0., 0., 0., 0., 0., 0., 0., 0., 0., - 0., 0., 128.]), - 'userStrs': {}, - 'userVars': {}} +{'variables': {'sysVars': {'K0': 0.0, + 'K1': 0.0, + 'K10': 0.0, + 'K11': 0.0, + 'K12': 0.0, + 'K13': 0.0, + 'K14': 0.0, + 'K15': 0.0, + 'K16': 0.0, + 'K17': 0.0, + 'K18': 0.0, + 'K19': 0.0, + 'K2': 0.0, + 'K20': 128.0, + 'K3': 0.0, + 'K4': 0.0, + 'K5': 0.0, + 'K6': 0.0, + 'K7': 0.0, + 'K8': 0.0, + 'K9': 0.0}, + 'userStrs': {}, + 'userVars': {}, + 'var_header': {'numSysVars': 21, + 'numUserStrs': 0, + 'numUserVars': 0}}, + 'version': 1} record 31: '\x95 Polar Graphs Demo, v3.01\n\n' record 32: -array([ 0.30000001, 0.5448544 , 0.77480197, 0.97584349, 1.13573945, +{'version': 2, + 'wave': {'bin_header': {'checksum': -25004, + 'noteSize': 0, + 'pictSize': 0, + 'wfmSize': 638}, + 'note': '', + 'padding': array([], dtype=float64), + 'wData': array([ 0.30000001, 0.5448544 , 0.77480197, 0.97584349, 1.13573945, 1.24475539, 1.2962544 , 1.28710103, 1.21785283, 1.09272552, 0.91933674, 0.7082426 , 0.47229454, 0.22585714, -0.01606643, -0.23874778, -0.42862982, -0.57415301, -0.6664573 , -0.69992352, @@ -691,46 +692,44 @@ 1.17415261, 1.0286293 , 0.83874667, 0.61606491, 0.37414294, 0.12770344, -0.1082412 , -0.31933719, -0.49272597, -0.61785328, -0.6871013 , -0.69625437, -0.64475471, -0.53574032, -0.37584305, - -0.17479956, 0.05514668, 0.30000135], dtype=float32) -{'checksum': -25004, - 'note': '', - 'noteSize': 0, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 638} -{'aModified': 0, - 'bname': array(['r', 'a', 'd', 'i', 'u', 's', 'D', 'a', 't', 'a', '', '', '', '', - '', '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 0.04908738521234052, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845545774, - 'next': 0, - 'npnts': 128, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': array([ 0.30000001, 0.5448544 , 0.77480197, 0.97584349]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')} + -0.17479956, 0.05514668, 0.30000135], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'radiusData', + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 0.04908738521234052, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845545774, + 'next': 0, + 'npnts': 128, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}}} record 33: -array([ 0. , 0.0494739 , 0.0989478 , 0.1484217 , 0.1978956 , +{'version': 2, + 'wave': {'bin_header': {'checksum': 28621, + 'noteSize': 0, + 'pictSize': 0, + 'wfmSize': 638}, + 'note': '', + 'padding': array([], dtype=float64), + 'wData': array([ 0. , 0.0494739 , 0.0989478 , 0.1484217 , 0.1978956 , 0.24736951, 0.29684341, 0.34631732, 0.3957912 , 0.44526511, 0.49473903, 0.54421294, 0.59368682, 0.6431607 , 0.69263464, 0.74210852, 0.79158241, 0.84105635, 0.89053023, 0.94000411, @@ -755,46 +754,54 @@ 5.44212914, 5.4916029 , 5.54107714, 5.5905509 , 5.64002466, 5.6894989 , 5.73897219, 5.78844643, 5.83792019, 5.88739443, 5.93686819, 5.98634195, 6.03581619, 6.08528948, 6.13476372, - 6.18423796, 6.23371172, 6.28318548], dtype=float32) -{'checksum': 28621, - 'note': '', - 'noteSize': 0, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 638} -{'aModified': 0, - 'bname': array(['a', 'n', 'g', 'l', 'e', 'D', 'a', 't', 'a', '', '', '', '', '', '', - '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 0.04908738521234052, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845470039, - 'next': 0, - 'npnts': 128, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': array([ 0. , 0.0494739, 0.0989478, 0.1484217]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')} + 6.18423796, 6.23371172, 6.28318548], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'angleData', + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 0.04908738521234052, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845470039, + 'next': 0, + 'npnts': 128, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}}} record 34: -array([ 1.83690956e-17, 2.69450769e-02, 7.65399113e-02, +{'version': 5, + 'wave': {'bin_header': {'checksum': 23021, + 'dataEUnitsSize': 0, + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formulaSize': 80, + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'wfmSize': 832}, + 'data_units': '', + 'dimension_units': '', + 'formula': ' PolarRadiusFunction(radiusData,1,0) * cos(PolarAngleFunction(angleData,3,1,2))', + 'labels': [[], [], [], []], + 'note': '', + 'sIndices': array([], dtype=float64), + 'wData': array([ 1.83690956e-17, 2.69450769e-02, 7.65399113e-02, 1.44305170e-01, 2.23293692e-01, 3.04783821e-01, 3.79158467e-01, 4.36888516e-01, 4.69528973e-01, 4.70633775e-01, 4.36502904e-01, 3.66688997e-01, @@ -836,70 +843,68 @@ 1.51621893e-01, 2.12215677e-01, 2.38205954e-01, 2.33226836e-01, 2.03656554e-01, 1.57870770e-01, 1.05330117e-01, 5.55786416e-02, 1.72677450e-02, - -2.72719120e-03, 5.24539061e-08], dtype=float32) -{'checksum': 23021, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': 'PolarRadiusFunction(radiusData,1,0) * cos(PolarAngleFunction(angleData,3,1,2))\x00', - 'formulaSize': 80, - 'note': '', - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 832} -{'aModified': 0, - 'bname': array(['W', '_', 'p', 'l', 'r', 'X', '5', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 24, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], + -2.72719120e-03, 5.24539061e-08], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'W_plrX5', + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 24, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 0, - 'formula': 8054500, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([128, 0, 0, 0]), - 'next': 8054516, - 'npnts': 128, - 'sIndices': 0, - 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 1.8369095638207904e-17, - 'wModified': 0, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0} + 'fileName': 0, + 'formula': 8054500, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([128, 0, 0, 0]), + 'next': 8054516, + 'npnts': 128, + 'sIndices': 0, + 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 0, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}}} record 35: -array([ 0.30000001, 0.54418772, 0.77101213, 0.96511477, 1.1135726 , +{'version': 5, + 'wave': {'bin_header': {'checksum': -9146, + 'dataEUnitsSize': 0, + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formulaSize': 80, + 'noteSize': 82, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'wfmSize': 832}, + 'data_units': '', + 'dimension_units': '', + 'formula': ' PolarRadiusFunction(radiusData,1,0) * sin(PolarAngleFunction(angleData,3,1,2))', + 'labels': [[], [], [], []], + 'note': 'shadowX=W_plrX5,appendRadius=radiusData,appendAngleData=angleData,angleDataUnits=2', + 'sIndices': array([], dtype=float64), + 'wData': array([ 0.30000001, 0.54418772, 0.77101213, 0.96511477, 1.1135726 , 1.20686483, 1.23956215, 1.21068466, 1.12370288, 0.98618096, 0.80910152, 0.60592639, 0.39147732, 0.18073183, -0.01236418, -0.17596789, -0.30120692, -0.38277394, -0.41920158, -0.41280419, @@ -924,70 +929,58 @@ 0.78277934, 0.72283876, 0.6181944 , 0.47410288, 0.29939076, 0.10585135, -0.09260413, -0.28104633, -0.44468346, -0.57008827, -0.64630753, -0.66580337, -0.62512833, -0.52528399, -0.37171093, - -0.17394456, 0.0550792 , 0.30000135], dtype=float32) -{'checksum': -9146, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': 'PolarRadiusFunction(radiusData,1,0) * sin(PolarAngleFunction(angleData,3,1,2))\x00', - 'formulaSize': 80, - 'note': 'shadowX=W_plrX5,appendRadius=radiusData,appendAngleData=angleData,angleDataUnits=2', - 'noteSize': 82, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 832} -{'aModified': 0, - 'bname': array(['W', '_', 'p', 'l', 'r', 'Y', '5', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 26, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], + -0.17394456, 0.0550792 , 0.30000135], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'W_plrY5', + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 26, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 0, - 'formula': 8054532, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([128, 0, 0, 0]), - 'next': 8084972, - 'npnts': 128, - 'sIndices': 0, - 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 0.30000001192092896, - 'wModified': 0, - 'waveNoteH': 7996608, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0} + 'fileName': 0, + 'formula': 8054532, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([128, 0, 0, 0]), + 'next': 8084972, + 'npnts': 128, + 'sIndices': 0, + 'sfA': array([ 0.04908739, 1. , 1. , 1. ]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 0, + 'waveNoteH': 7996608, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}}} record 36: -array([ 0.2617994 , 0.27842158, 0.29504377, 0.31166595, 0.32828814, +{'version': 2, + 'wave': {'bin_header': {'checksum': 14307, + 'noteSize': 0, + 'pictSize': 0, + 'wfmSize': 382}, + 'note': '', + 'padding': array([], dtype=float64), + 'wData': array([ 0.2617994 , 0.27842158, 0.29504377, 0.31166595, 0.32828814, 0.34491032, 0.36153251, 0.3781547 , 0.39477688, 0.41139907, 0.42802125, 0.44464344, 0.46126559, 0.47788778, 0.49450997, 0.51113212, 0.52775431, 0.54437649, 0.56099868, 0.57762086, @@ -999,46 +992,44 @@ 1.00979757, 1.02641988, 1.04304194, 1.05966425, 1.07628632, 1.09290862, 1.10953069, 1.12615299, 1.14277506, 1.15939736, 1.17601943, 1.19264174, 1.2092638 , 1.22588611, 1.24250817, - 1.25913048, 1.27575254, 1.29237485, 1.30899692], dtype=float32) -{'checksum': 14307, - 'note': '', - 'noteSize': 0, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 382} -{'aModified': 0, - 'bname': array(['a', 'n', 'g', 'l', 'e', 'Q', '1', '', '', '', '', '', '', '', '', - '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845473705, - 'next': 0, - 'npnts': 64, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': array([ 0.2617994 , 0.27842158, 0.29504377, 0.31166595]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')} + 1.25913048, 1.27575254, 1.29237485, 1.30899692], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'angleQ1', + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845473705, + 'next': 0, + 'npnts': 64, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}}} record 37: -array([ -8.34064484, -7.66960144, -6.62294245, -6.82878971, +{'version': 2, + 'wave': {'bin_header': {'checksum': -12080, + 'noteSize': 0, + 'pictSize': 0, + 'wfmSize': 382}, + 'note': '', + 'padding': array([], dtype=float64), + 'wData': array([ -8.34064484, -7.66960144, -6.62294245, -6.82878971, -8.6383152 , -11.20019722, -13.83398628, -15.95139503, -16.18096733, -13.58062267, -9.26843071, -5.34649038, -3.01010084, -2.30953455, -2.73682952, -3.72112942, @@ -1053,46 +1044,54 @@ -4.54975414, -4.52917624, -3.99160147, -3.1971693 , -2.93472862, -3.47230864, -4.7322526 , -6.80173016, -9.08601665, -10.00928402, -8.87677383, -6.88120317, - -5.61007977, -5.6351161 , -6.41880989, -6.8738699 ], dtype=float32) -{'checksum': -12080, - 'note': '', - 'noteSize': 0, - 'pictSize': 0, - 'version': 2, - 'wfmSize': 382} -{'aModified': 0, - 'bname': array(['r', 'a', 'd', 'i', 'u', 's', 'Q', '1', '', '', '', '', '', '', '', - '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 0, - 'fileName': 0, - 'formula': 0, - 'fsValid': 0, - 'hsA': 1.0, - 'hsB': 0.0, - 'kindBits': '\x00', - 'modDate': 2845473634, - 'next': 0, - 'npnts': 64, - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': array([-8.34064484, -7.66960144, -6.62294245, -6.82878971]), - 'wModified': 0, - 'wUnused': array(['', ''], - dtype='|S1'), - 'waveNoteH': 0, - 'whVersion': 0, - 'xUnits': array(['', '', '', ''], - dtype='|S1')} + -5.61007977, -5.6351161 , -6.41880989, -6.8738699 ], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'radiusQ1', + 'botFullScale': 0.0, + 'creationDate': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 0, + 'fileName': 0, + 'formula': 0, + 'fsValid': 0, + 'hsA': 1.0, + 'hsB': 0.0, + 'kindBits': '\x00', + 'modDate': 2845473634, + 'next': 0, + 'npnts': 64, + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 0, + 'wUnused': array(['', ''], + dtype='|S1'), + 'waveNoteH': 0, + 'whVersion': 0, + 'xUnits': array(['', '', '', ''], + dtype='|S1')}}} record 38: -array([ 30.58058929, 31.08536911, 31.93481636, 31.57315445, +{'version': 5, + 'wave': {'bin_header': {'checksum': -5745, + 'dataEUnitsSize': 0, + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formulaSize': 78, + 'noteSize': 0, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'wfmSize': 576}, + 'data_units': '', + 'dimension_units': '', + 'formula': ' PolarRadiusFunction(radiusQ1,1,-40) * cos(PolarAngleFunction(angleQ1,2,2,2))', + 'labels': [[], [], [], []], + 'note': '', + 'sIndices': array([], dtype=float64), + 'wData': array([ 30.58058929, 31.08536911, 31.93481636, 31.57315445, 29.68683434, 27.10366058, 24.47453499, 22.3495121 , 21.98692894, 24.21500397, 27.95923996, 31.28394508, 33.12408066, 33.46794128, 32.79909515, 31.64211464, @@ -1107,70 +1106,68 @@ 17.34101677, 16.83446693, 16.56042671, 16.38027191, 15.94310474, 15.16159916, 14.10328865, 12.76812935, 11.41363049, 10.60795975, 10.52314186, 10.67826462, - 10.5454855 , 9.99268055, 9.22939587, 8.5736742 ], dtype=float32) -{'checksum': -5745, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': 'PolarRadiusFunction(radiusQ1,1,-40) * cos(PolarAngleFunction(angleQ1,2,2,2))\x00', - 'formulaSize': 78, - 'note': '', - 'noteSize': 0, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 576} -{'aModified': 0, - 'bname': array(['W', '_', 'p', 'l', 'r', 'X', '6', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 30, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], + 10.5454855 , 9.99268055, 9.22939587, 8.5736742 ], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'W_plrX6', + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 30, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 0, - 'formula': 8052116, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([64, 0, 0, 0]), - 'next': 8324392, - 'npnts': 64, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 30.580589294433594, - 'wModified': 0, - 'waveNoteH': 0, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0} + 'fileName': 0, + 'formula': 8052116, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([64, 0, 0, 0]), + 'next': 8324392, + 'npnts': 64, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 0, + 'waveNoteH': 0, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}}} record 39: -array([ 8.19404411, 8.88563347, 9.70543861, 10.17177773, +{'version': 5, + 'wave': {'bin_header': {'checksum': -16604, + 'dataEUnitsSize': 0, + 'dimEUnitsSize': array([0, 0, 0, 0]), + 'dimLabelsSize': array([0, 0, 0, 0]), + 'formulaSize': 78, + 'noteSize': 78, + 'optionsSize1': 0, + 'optionsSize2': 0, + 'sIndicesSize': 0, + 'wfmSize': 576}, + 'data_units': '', + 'dimension_units': '', + 'formula': ' PolarRadiusFunction(radiusQ1,1,-40) * sin(PolarAngleFunction(angleQ1,2,2,2))', + 'labels': [[], [], [], []], + 'note': 'shadowX=W_plrX6,appendRadius=radiusQ1,appendAngleData=angleQ1,angleDataUnits=2', + 'sIndices': array([], dtype=float64), + 'wData': array([ 8.19404411, 8.88563347, 9.70543861, 10.17177773, 10.11173058, 9.73756695, 9.25513077, 8.8788929 , 9.16085339, 10.56489944, 12.75579453, 14.90572262, 16.46352959, 17.33401871, 17.68511391, 17.74635315, @@ -1185,137 +1182,154 @@ 30.91939545, 31.22146797, 31.97431755, 32.95656204, 33.4611969 , 33.23248672, 32.3250885 , 30.64473915, 28.72983551, 28.05199242, 29.29024887, 31.3501091 , - 32.7331543 , 32.87995529, 32.28799438, 31.99738503], dtype=float32) -{'checksum': -16604, - 'dataEUnits': '', - 'dataEUnitsSize': 0, - 'dimEUnits': ['', '', '', ''], - 'dimEUnitsSize': array([0, 0, 0, 0]), - 'dimLabels': [[], [], [], []], - 'dimLabelsSize': array([0, 0, 0, 0]), - 'formula': 'PolarRadiusFunction(radiusQ1,1,-40) * sin(PolarAngleFunction(angleQ1,2,2,2))\x00', - 'formulaSize': 78, - 'note': 'shadowX=W_plrX6,appendRadius=radiusQ1,appendAngleData=angleQ1,angleDataUnits=2', - 'noteSize': 78, - 'optionsSize1': 0, - 'optionsSize2': 0, - 'sIndicesSize': 0, - 'version': 5, - 'wfmSize': 576} -{'aModified': 0, - 'bname': array(['W', '_', 'p', 'l', 'r', 'Y', '6', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''], - dtype='|S1'), - 'botFullScale': 0.0, - 'creationDate': 0, - 'dFolder': 7848580, - 'dLock': 0, - 'dataEUnits': 0, - 'dataUnits': array(['', '', '', ''], - dtype='|S1'), - 'depID': 32, - 'dimEUnits': array([0, 0, 0, 0]), - 'dimLabels': array([0, 0, 0, 0]), - 'dimUnits': array([['', '', '', ''], + 32.7331543 , 32.87995529, 32.28799438, 31.99738503], dtype=float32), + 'wave_header': {'aModified': 0, + 'bname': 'W_plrY6', + 'botFullScale': 0.0, + 'creationDate': 0, + 'dFolder': 7848580, + 'dLock': 0, + 'dataEUnits': 0, + 'dataUnits': array(['', '', '', ''], + dtype='|S1'), + 'depID': 32, + 'dimEUnits': array([0, 0, 0, 0]), + 'dimLabels': array([0, 0, 0, 0]), + 'dimUnits': array([['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']], dtype='|S1'), - 'fileName': 0, - 'formula': 7995612, - 'fsValid': 0, - 'kindBits': '\x00', - 'modDate': 2985072242, - 'nDim': array([64, 0, 0, 0]), - 'next': 0, - 'npnts': 64, - 'sIndices': 0, - 'sfA': array([ 1., 1., 1., 1.]), - 'sfB': array([ 0., 0., 0., 0.]), - 'srcFldr': 0, - 'swModified': 0, - 'topFullScale': 0.0, - 'type': 2, - 'useBits': '\x00', - 'wData': 8.19404411315918, - 'wModified': 0, - 'waveNoteH': 7998208, - 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - 'whVersion': 1, - 'whpad1': array(['', '', '', '', '', ''], - dtype='|S1'), - 'whpad2': 0, - 'whpad3': 0, - 'whpad4': 0} + 'fileName': 0, + 'formula': 7995612, + 'fsValid': 0, + 'kindBits': '\x00', + 'modDate': 2985072242, + 'nDim': array([64, 0, 0, 0]), + 'next': 0, + 'npnts': 64, + 'sIndices': 0, + 'sfA': array([ 1., 1., 1., 1.]), + 'sfB': array([ 0., 0., 0., 0.]), + 'srcFldr': 0, + 'swModified': 0, + 'topFullScale': 0.0, + 'type': 2, + 'useBits': '\x00', + 'wModified': 0, + 'waveNoteH': 7998208, + 'whUnused': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 'whVersion': 1, + 'whpad1': array(['', '', '', '', '', ''], + dtype='|S1'), + 'whpad2': 0, + 'whpad3': 0, + 'whpad4': 0}}} record 40: 'Packages' record 41: 'WMDataBase' record 42: -{'header': {'numSysVars': 21, - 'numUserStrs': 6, - 'numUserVars': 0, - 'version': 1}, - 'sysVars': array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., - 0., 0., 0., 0., 0., 0., 0., 0., 0., - 0., 0., 128.]), - 'userStrs': {'u_dataBase': ';PolarGraph0:,...,useCircles=2,maxArcLine=6;', - 'u_dbBadStringChars': ',;=:', - 'u_dbCurrBag': 'PolarGraph1', - 'u_dbCurrContents': ',appendRadius=radiusQ1,...,useCircles=2,maxArcLine=6;', - 'u_dbReplaceBadChars': '\xa9\xae\x99\x9f', - 'u_str': '2'}, - 'userVars': {}} +{'variables': {'sysVars': {'K0': 0.0, + 'K1': 0.0, + 'K10': 0.0, + 'K11': 0.0, + 'K12': 0.0, + 'K13': 0.0, + 'K14': 0.0, + 'K15': 0.0, + 'K16': 0.0, + 'K17': 0.0, + 'K18': 0.0, + 'K19': 0.0, + 'K2': 0.0, + 'K20': 128.0, + 'K3': 0.0, + 'K4': 0.0, + 'K5': 0.0, + 'K6': 0.0, + 'K7': 0.0, + 'K8': 0.0, + 'K9': 0.0}, + 'userStrs': {'u_dataBase': ';PolarGraph0:,...,useCircles=2,maxArcLine=6;', + 'u_dbBadStringChars': ',;=:', + 'u_dbCurrBag': 'PolarGraph1', + 'u_dbCurrContents': ',appendRadius=radiusQ1,...,useCircles=2,maxArcLine=6;', + 'u_dbReplaceBadChars': '\xa9\xae\x99\x9f', + 'u_str': '2'}, + 'userVars': {}, + 'var_header': {'numSysVars': 21, + 'numUserStrs': 6, + 'numUserVars': 0}}, + 'version': 1} record 43: '' record 44: 'PolarGraphs' record 45: -{'header': {'numSysVars': 21, - 'numUserStrs': 10, - 'numUserVars': 28, - 'version': 1}, - 'sysVars': array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., - 0., 0., 0., 0., 0., 0., 0., 0., 0., - 0., 0., 128.]), - 'userStrs': {'u_colorList': 'black;blue;green;cyan;red;magenta;yellow;white;special', - 'u_debugStr': 'Turn Debugging On', - 'u_polAngleAxesWherePop': 'Off;Radius Start;Radius End;Radius Start and End;All Major Radii;At Listed Radii', - 'u_polAngleUnitsPop': 'deg;rad', - 'u_polLineStylePop': 'solid;dash 1;dash 2;dash 3;dash 4;dash 5;dash 6;dash 7;dash 8;dash 9;dash 10;dash 11;dash 12;dash 13;dash 14;dash 15;dash 16;dash 17;', - 'u_polOffOn': 'Off;On', - 'u_polRadAxesWherePop': ' Off; Angle Start; Angle Middle; Angle End; Angle Start and End; 0; 90; 180; -90; 0, 90; 90, 180; -180, -90; -90, 0; 0, 180; 90, -90; 0, 90, 180, -90; All Major Angles; At Listed Angles', - 'u_polRotPop': ' -90; 0; +90; +180', - 'u_popup': '', - 'u_prompt': ''}, - 'userVars': {'V_bottom': 232.0, - 'V_left': 1.0, - 'V_max': 2.4158518093414401, - 'V_min': -2.1848498883412, - 'V_right': 232.0, - 'V_top': 1.0, - 'u_UniqWaveNdx': 8.0, - 'u_UniqWinNdx': 3.0, - 'u_angle0': 0.0, - 'u_angleRange': 6.2831853071795862, - 'u_debug': 0.0, - 'u_majorDelta': 0.0, - 'u_numPlaces': 0.0, - 'u_polAngle0': 0.26179938779914941, - 'u_polAngleRange': 1.0471975511965976, - 'u_polInnerRadius': -20.0, - 'u_polMajorAngleInc': 0.26179938779914941, - 'u_polMajorRadiusInc': 10.0, - 'u_polMinorAngleTicks': 3.0, - 'u_polMinorRadiusTicks': 1.0, - 'u_polOuterRadius': 0.0, - 'u_segsPerMinorArc': 3.0, - 'u_tickDelta': 0.0, - 'u_var': 0.0, - 'u_x1': 11.450159535018935, - 'u_x2': 12.079591517721363, - 'u_y1': 42.732577139459856, - 'u_y2': 45.081649278814126}} +{'variables': {'sysVars': {'K0': 0.0, + 'K1': 0.0, + 'K10': 0.0, + 'K11': 0.0, + 'K12': 0.0, + 'K13': 0.0, + 'K14': 0.0, + 'K15': 0.0, + 'K16': 0.0, + 'K17': 0.0, + 'K18': 0.0, + 'K19': 0.0, + 'K2': 0.0, + 'K20': 128.0, + 'K3': 0.0, + 'K4': 0.0, + 'K5': 0.0, + 'K6': 0.0, + 'K7': 0.0, + 'K8': 0.0, + 'K9': 0.0}, + 'userStrs': {'u_colorList': 'black;blue;green;cyan;red;magenta;yellow;white;special', + 'u_debugStr': 'Turn Debugging On', + 'u_polAngleAxesWherePop': 'Off;Radius Start;Radius End;Radius Start and End;All Major Radii;At Listed Radii', + 'u_polAngleUnitsPop': 'deg;rad', + 'u_polLineStylePop': 'solid;dash 1;dash 2;dash 3;dash 4;dash 5;dash 6;dash 7;dash 8;dash 9;dash 10;dash 11;dash 12;dash 13;dash 14;dash 15;dash 16;dash 17;', + 'u_polOffOn': 'Off;On', + 'u_polRadAxesWherePop': ' Off; Angle Start; Angle Middle; Angle End; Angle Start and End; 0; 90; 180; -90; 0, 90; 90, 180; -180, -90; -90, 0; 0, 180; 90, -90; 0, 90, 180, -90; All Major Angles; At Listed Angles', + 'u_polRotPop': ' -90; 0; +90; +180', + 'u_popup': '', + 'u_prompt': ''}, + 'userVars': {'V_bottom': 232.0, + 'V_left': 1.0, + 'V_max': 2.4158518093414401, + 'V_min': -2.1848498883412, + 'V_right': 232.0, + 'V_top': 1.0, + 'u_UniqWaveNdx': 8.0, + 'u_UniqWinNdx': 3.0, + 'u_angle0': 0.0, + 'u_angleRange': 6.2831853071795862, + 'u_debug': 0.0, + 'u_majorDelta': 0.0, + 'u_numPlaces': 0.0, + 'u_polAngle0': 0.26179938779914941, + 'u_polAngleRange': 1.0471975511965976, + 'u_polInnerRadius': -20.0, + 'u_polMajorAngleInc': 0.26179938779914941, + 'u_polMajorRadiusInc': 10.0, + 'u_polMinorAngleTicks': 3.0, + 'u_polMinorRadiusTicks': 1.0, + 'u_polOuterRadius': 0.0, + 'u_segsPerMinorArc': 3.0, + 'u_tickDelta': 0.0, + 'u_var': 0.0, + 'u_x1': 11.450159535018935, + 'u_x2': 12.079591517721363, + 'u_y1': 42.732577139459856, + 'u_y2': 45.081649278814126}, + 'var_header': {'numSysVars': 21, + 'numUserStrs': 10, + 'numUserVars': 28}}, + 'version': 1} record 46: '' record 47: @@ -1418,18 +1432,16 @@ _this_dir = os.path.dirname(__file__) _data_dir = os.path.join(_this_dir, 'data') -def dumpibw(filename, strict=True): +def dumpibw(filename): sys.stderr.write('Testing {}\n'.format(filename)) path = os.path.join(_data_dir, filename) - data,bin_info,wave_info = loadibw(path, strict=strict) + data = loadibw(path) pprint(data) - pprint(bin_info) - pprint(wave_info) -def dumppxp(filename, strict=True): +def dumppxp(filename): sys.stderr.write('Testing {}\n'.format(filename)) path = os.path.join(_data_dir, filename) - records,filesystem = loadpxp(path, strict=strict) + records,filesystem = loadpxp(path) for i,record in enumerate(records): print('record {}:'.format(i)) if isinstance(record, (FolderStartRecord, FolderEndRecord)): @@ -1440,8 +1452,6 @@ def dumppxp(filename, strict=True): pprint(record.variables) elif isinstance(record, WaveRecord): pprint(record.wave) - pprint(record.bin_info) - pprint(record.wave_info) else: pprint(record) print('\nfilesystem:') From e1ebd507208067303a0838e54ece88e76fef9691 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 20 Jul 2012 15:22:34 -0400 Subject: [PATCH 34/76] Update igorbinarywave.py to use dynamic structure interface. --- bin/igorbinarywave.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bin/igorbinarywave.py b/bin/igorbinarywave.py index f3da1cd..9d0fa0e 100755 --- a/bin/igorbinarywave.py +++ b/bin/igorbinarywave.py @@ -4,14 +4,15 @@ "IBW -> ASCII conversion" +import logging import optparse import pprint import sys import numpy -from igor import __version__ -from igor.binarywave import loadibw +from igor import __version__, LOG +from igor.binarywave import load p = optparse.OptionParser(version=__version__) @@ -22,8 +23,6 @@ default='-', help='File for ASCII output.') p.add_option('-v', '--verbose', dest='verbose', default=0, action='count', help='Increment verbosity') -p.add_option('-n', '--not-strict', dest='strict', default=True, - action='store_false', help='Attempt to parse invalid IBW files.') options,args = p.parse_args() @@ -34,8 +33,13 @@ if options.outfile == '-': options.outfile = sys.stdout -data,bin_info,wave_info = loadibw(options.infile, strict=options.strict) -numpy.savetxt(options.outfile, data, fmt='%g', delimiter='\t') +if options.verbose > 1: + log_levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG] + log_level = log_levels[min(options.verbose-1, len(log_levels)-1)] + LOG.setLevel(log_level) + +wave = load(options.infile) +numpy.savetxt(options.outfile, wave['wave']['wData'], fmt='%g', delimiter='\t') if options.verbose > 0: - pprint.pprint(bin_info) - pprint.pprint(wave_info) + wave['wave'].pop('wData') + pprint.pprint(wave) From ccbf04264b8b666ea7967d146a3151279bff123f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 20 Jul 2012 15:25:22 -0400 Subject: [PATCH 35/76] Add COPYING file with GPLv3 text. --- COPYING | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++ MANIFEST.in | 1 + README | 3 +- 3 files changed, 677 insertions(+), 1 deletion(-) create mode 100644 COPYING create mode 100644 MANIFEST.in diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..eb762f3 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include COPYING diff --git a/README b/README index 825bc22..215de4f 100644 --- a/README +++ b/README @@ -90,7 +90,8 @@ Licence ======= This project is distributed under the `GNU General Public License -Version 3`_ or greater. +Version 3`_ or greater, see the ``COPYING`` file distributed with the +project for details. Maintenance =========== From 8b9929fd3b10750d81ab44207d2cf43d1a5db0e1 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 20 Jul 2012 15:27:23 -0400 Subject: [PATCH 36/76] Point out that test/data is not bundled. --- README | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README b/README index 215de4f..bdad784 100644 --- a/README +++ b/README @@ -86,6 +86,10 @@ Run internal unit tests with:: $ nosetests --with-doctest --doctest-tests igor test +The data in the ``test/data`` directory is in the Git repository, but +it is not bundled with the source code. If you want the test data, +you'll have to clone the Git repository or download a snapshot. + Licence ======= From 6a09fdfcb9bfc94879f4ad19cd633120ff1e0890 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 20 Jul 2012 16:03:28 -0400 Subject: [PATCH 37/76] Use LOG to print helpful messages in test.py. --- test/test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test.py b/test/test.py index 1567ff3..4a76476 100644 --- a/test/test.py +++ b/test/test.py @@ -1419,8 +1419,8 @@ import os.path from pprint import pformat -import sys +from igor import LOG from igor.binarywave import load as loadibw from igor.packed import load as loadpxp from igor.record.base import TextRecord @@ -1433,13 +1433,13 @@ _data_dir = os.path.join(_this_dir, 'data') def dumpibw(filename): - sys.stderr.write('Testing {}\n'.format(filename)) + LOG.info('Testing {}\n'.format(filename)) path = os.path.join(_data_dir, filename) data = loadibw(path) pprint(data) def dumppxp(filename): - sys.stderr.write('Testing {}\n'.format(filename)) + LOG.info('Testing {}\n'.format(filename)) path = os.path.join(_data_dir, filename) records,filesystem = loadpxp(path) for i,record in enumerate(records): From a2151cb15bcb281e94b75d3d4206f0c0a451fbd0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 20 Jul 2012 16:05:18 -0400 Subject: [PATCH 38/76] Add test-igorpy.py for testing igor.igorpy. --- igor/igorpy.py | 7 +- test/test-igorpy.py | 184 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 test/test-igorpy.py diff --git a/igor/igorpy.py b/igor/igorpy.py index f68c7fc..ad4a09a 100644 --- a/igor/igorpy.py +++ b/igor/igorpy.py @@ -1,6 +1,5 @@ # This program is in the public domain -""" -IGOR file reader. +"""`igor.py` compatibility layer on top of the `igor` package. igor.load('filename') or igor.loads('data') loads the content of an igore file into memory as a folder structure. @@ -14,7 +13,9 @@ The usual igor folder types are given in the technical reports PTN003.ifn and TN003.ifn. """ -__version__="0.9" +from __future__ import absolute_import + +__version__='1.0' import struct import numpy diff --git a/test/test-igorpy.py b/test/test-igorpy.py new file mode 100644 index 0000000..ab9e9a8 --- /dev/null +++ b/test/test-igorpy.py @@ -0,0 +1,184 @@ +# Copyright + +r"""Test the igor.igorpy compatibility layer by loading sample files. + +>>> from pprint import pprint +>>> import igor.igorpy as igor + +Load a packed experiment: + +>>> path = data_path('polar-graphs-demo.pxp') +>>> d = igor.load(path) +>>> print(d) + +>>> dir(d) # doctest: +ELLIPSIS +['Packages', 'W_plrX5', 'W_plrX6', ..., 'radiusData', 'radiusQ1'] + + +Navigation: + +>>> print(d.Packages) + +>>> print(d[0]) # doctest: +ELLIPSIS + + + +Variables: + +>>> v = d[0] +>>> dir(v) # doctest: +ELLIPSIS +['__class__', ..., 'depstr', 'depvar', 'format', 'sysvar', 'userstr', 'uservar'] +>>> v.depstr +{} +>>> v.depvar +{} +>>> v.format() +'' +>>> pprint(v.sysvar) # doctest: +REPORT_UDIFF +{'K0': 0.0, + 'K1': 0.0, + 'K10': 0.0, + 'K11': 0.0, + 'K12': 0.0, + 'K13': 0.0, + 'K14': 0.0, + 'K15': 0.0, + 'K16': 0.0, + 'K17': 0.0, + 'K18': 0.0, + 'K19': 0.0, + 'K2': 0.0, + 'K20': 128.0, + 'K3': 0.0, + 'K4': 0.0, + 'K5': 0.0, + 'K6': 0.0, + 'K7': 0.0, + 'K8': 0.0, + 'K9': 0.0} +>>> v.userstr +{} +>>> v.uservar +{} + + +Waves: + +>>> d.W_plrX5 + +>>> dir(d.W_plrX5) # doctest: +ELLIPSIS +['__array__', ..., 'axis', 'axis_units', 'data', ..., 'name', 'notes'] +>>> d.W_plrX5.axis # doctest: +ELLIPSIS +[array([ 0.04908739, 0.04870087, 0.04831436, 0.04792784, 0.04754133, + 0.04715481, 0.0467683 , 0.04638178, 0.04599527, 0.04560875, + ... + 0.00077303, 0.00038651, 0. ]), array([], dtype=float64), array([], dtype=float64), array([], dtype=float64)] +>>> d.W_plrX5.axis_units +('', '', '', '') +>>> d.W_plrX5.data # doctest: +ELLIPSIS +array([ 1.83690956e-17, 2.69450769e-02, 7.65399113e-02, + 1.44305170e-01, 2.23293692e-01, 3.04783821e-01, + ... + -2.72719120e-03, 5.24539061e-08], dtype=float32) + + +Dump the whole thing: + +>>> print(d.format()) +root + + + radiusData data (128) + angleData data (128) + W_plrX5 data (128) + W_plrY5 data (128) + angleQ1 data (64) + radiusQ1 data (64) + W_plrX6 data (64) + W_plrY6 data (64) + Packages + WMDataBase + + PolarGraphs + + + + + + +Load a packed experiment without ignoring unknown records: + +>>> d = igor.load(path, ignore_unknown=False) +>>> print(d.format()) +root + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + radiusData data (128) + angleData data (128) + W_plrX5 data (128) + W_plrY5 data (128) + angleQ1 data (64) + radiusQ1 data (64) + W_plrX6 data (64) + W_plrY6 data (64) + Packages + WMDataBase + + PolarGraphs + + + + + + +Try to load a binary wave: + +>>> path = data_path('mac-double.ibw') +>>> d = igor.load(path) +Traceback (most recent call last): + ... +IOError: final record too long; bad pxp file? +""" + +import os.path + +from igor import LOG + + +_this_dir = os.path.dirname(__file__) +_data_dir = os.path.join(_this_dir, 'data') + +def data_path(filename): + LOG.info('Testing igorpy compatibility {}\n'.format(filename)) + path = os.path.join(_data_dir, filename) + return path From 1d1e8810333ab64d575bef2a723269ed42039083 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 07:48:54 -0400 Subject: [PATCH 39/76] Add 'not enough data...' errors to packed.load(). --- igor/packed.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/igor/packed.py b/igor/packed.py index 564725a..1b0fec5 100644 --- a/igor/packed.py +++ b/igor/packed.py @@ -55,6 +55,10 @@ def load(filename, strict=True, ignore_unknown=True): b = buffer(f.read(PackedFileRecordHeader.size)) if not b: break + if len(b) < PackedFileRecordHeader.size: + raise ValueError( + ('not enough data for the next record header ({} < {})' + ).format(len(b), PackedFileRecordHeader.size)) _LOG.debug('reading a new packed experiment file record') header = PackedFileRecordHeader.unpack_from(b) if header['version'] and not byte_order: @@ -70,6 +74,10 @@ def load(filename, strict=True, ignore_unknown=True): _LOG.debug( 'reordered version: {}'.format(header['version'])) data = buffer(f.read(header['numDataBytes'])) + if len(data) < header['numDataBytes']: + raise ValueError( + ('not enough data for the next record ({} < {})' + ).format(len(b), header['numDataBytes'])) record_type = _RECORD_TYPE.get( header['recordType'] & PACKEDRECTYPE_MASK, _UnknownRecord) _LOG.debug('the new record has type {} ({}).'.format( From fe7006e3e2d741b6d80767b1aac53394ff1e7e76 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 07:50:09 -0400 Subject: [PATCH 40/76] Replace igor.igorpy parsing with translations from igor.packed.load. --- igor/igorpy.py | 398 ++++++++++++++----------------------------------- 1 file changed, 114 insertions(+), 284 deletions(-) diff --git a/igor/igorpy.py b/igor/igorpy.py index ad4a09a..0de34d5 100644 --- a/igor/igorpy.py +++ b/igor/igorpy.py @@ -14,89 +14,54 @@ PTN003.ifn and TN003.ifn. """ from __future__ import absolute_import +import io as _io +import re as _re -__version__='1.0' +import numpy as _numpy -import struct -import numpy -import sys -import re +from .binarywave import MAXDIMS as _MAXDIMS +from .packed import load as _load +from .record.base import UnknownRecord as _UnknownRecord +from .record.folder import FolderStartRecord as _FolderStartRecord +from .record.folder import FolderEndRecord as _FolderEndRecord +from .record.history import HistoryRecord as _HistoryRecord +from .record.history import GetHistoryRecord as _GetHistoryRecord +from .record.history import RecreationRecord as _RecreationRecord +from .record.packedfile import PackedFileRecord as _PackedFileRecord +from .record.procedure import ProcedureRecord as _ProcedureRecord +from .record.wave import WaveRecord as _WaveRecord +from .record.variables import VariablesRecord as _VariablesRecord + + +__version__='0.10' -decode = lambda s: s.decode(sys.getfilesystemencoding()) PYKEYWORDS = set(('and','as','assert','break','class','continue', 'def','elif','else','except','exec','finally', 'for','global','if','import','in','is','lambda', 'or','pass','print','raise','return','try','with', 'yield')) -PYID = re.compile(r"^[^\d\W]\w*$", re.UNICODE) +PYID = _re.compile(r"^[^\d\W]\w*$", _re.UNICODE) def valid_identifier(s): """Check if a name is a valid identifier""" return PYID.match(s) and s not in PYKEYWORDS -NUMTYPE = { - 1: numpy.complex64, - 2: numpy.float32, - 3: numpy.complex64, - 4: numpy.float64, - 5: numpy.complex128, - 8: numpy.int8, - 16: numpy.int16, - 32: numpy.int32, - 64+ 8: numpy.uint8, - 64+16: numpy.uint16, - 64+32: numpy.uint32, -} - -ORDER_NUMTYPE = { - 1: 'c8', - 2: 'f4', - 3: 'c8', - 4: 'f8', - 5: 'c16', - 8: 'i1', - 16: 'i2', - 32: 'i4', - 64+ 8: 'u2', - 64+16: 'u2', - 64+32: 'u4', -} - class IgorObject(object): """ Parent class for all objects the parser can return """ pass -class Formula(IgorObject): - def __init__(self, formula, value): - self.formula = formula - self.value = value - class Variables(IgorObject): """ Contains system numeric variables (e.g., K0) and user numeric and string variables. """ - def __init__(self, data, order): - version, = struct.unpack(order+"h",data[:2]) - if version == 1: - pos = 8 - nSysVar, nUserVar, nUserStr \ - = struct.unpack(order+"hhh",data[2:pos]) - nDepVar, nDepStr = 0, 0 - elif version == 2: - pos = 12 - nSysVar, nUservar, nUserStr, nDepVar, nDepStr \ - = struct.unpack(order+"hhh",data[2:pos]) - else: - raise ValueError("Unknown variable record version "+str(version)) - self.sysvar, pos = _parse_sys_numeric(nSysVar, order, data, pos) - self.uservar, pos = _parse_user_numeric(nUserVar, order, data, pos) - if version == 1: - self.userstr, pos = _parse_user_string1(nUserStr, order, data, pos) - else: - self.userstr, pos = _parse_user_string2(nUserStr, order, data, pos) - self.depvar, pos = _parse_dep_numeric(nDepVar, order, data, pos) - self.depstr, pos = _parse_dep_string(nDepStr, order, data, pos) + def __init__(self, record): + self.sysvar = record.variables['variables']['sysVars'] + self.uservar = record.variables['variables']['userVars'] + self.userstr = record.variables['variables']['userStrs'] + self.depvar = record.variables['variables'].get('dependentVars', {}) + self.depstr = record.variables['variables'].get('dependentStrs', {}) + def format(self, indent=0): return " "*indent+""\ %(len(self.sysvar), @@ -107,7 +72,8 @@ class History(IgorObject): """ Contains the experiment's history as plain text. """ - def __init__(self, data, order): self.data = data + def __init__(self, data): + self.data = data def format(self, indent=0): return " "*indent+"" @@ -115,101 +81,33 @@ class Wave(IgorObject): """ Contains the data for a wave """ - def __init__(self, data, order): - version, = struct.unpack(order+'h',data[:2]) - if version == 1: - pos = 8 - extra_offset,checksum = struct.unpack(order+'ih',data[2:pos]) - formula_size = note_size = pic_size = 0 - elif version == 2: - pos = 16 - extra_offset,note_size,pic_size,checksum \ - = struct.unpack(order+'iiih',data[2:pos]) - formula_size = 0 - elif version == 3: - pos = 20 - extra_offset,note_size,formula_size,pic_size,checksum \ - = struct.unpack(order+'iiiih',data[2:pos]) - elif version == 5: - checksum,extra_offset,formula_size,note_size, \ - = struct.unpack(order+'hiii',data[2:16]) - Esize = struct.unpack(order+'iiiiiiiii',data[16:52]) - textindsize, = struct.unpack('i',data[52:56]) - picsize = 0 - pos = 64 - else: - raise ValueError("unknown wave version "+str(version)) - extra_offset += pos - - if version in (1,2,3): - type, = struct.unpack(order+'h',data[pos:pos+2]) - name = data[pos+6:data.find(chr(0),pos+6,pos+26)] - #print "name3",name,type - data_units = data[pos+34:data.find(chr(0),pos+34,pos+38)] - xaxis = data[pos+38:data.find(chr(0),pos+38,pos+42)] - points, = struct.unpack(order+'i',data[pos+42:pos+46]) - hsA,hsB = struct.unpack(order+'dd',data[pos+48:pos+64]) - fsValid,fsTop,fsBottom \ - = struct.unpack(order+'hdd',data[pos+70:pos+88]) - created,_,modified = struct.unpack(order+'IhI',data[pos+98:pos+108]) - pos += 110 - dims = (points,0,0,0) - sf = (hsA,0,0,0,hsB,0,0,0) - axis_units = (xaxis,"","","") - else: # version is 5 - created,modified,points,type \ - = struct.unpack(order+'IIih',data[pos+4:pos+18]) - name = data[pos+28:data.find(chr(0),pos+28,pos+60)] - #print "name5",name,type - dims = struct.unpack(order+'iiii',data[pos+68:pos+84]) - sf = struct.unpack(order+'dddddddd',data[pos+84:pos+148]) - data_units = data[pos+148:data.find(chr(0),pos+148,pos+152)] - axis_units = tuple(data[pos+152+4*i - : data.find(chr(0),pos+152+4*i,pos+156+4*i)] - for i in range(4)) - fsValid,_,fsTop,fsBottom \ - = struct.unpack(order+'hhdd',data[pos+172:pos+192]) - pos += 320 - - if type == 0: - text = data[pos:extra_offset] - textind = numpy.fromstring(data[-textindsize:], order+'i') - textind = numpy.hstack((0,textind)) - value = [text[textind[i]:textind[i+1]] - for i in range(len(textind)-1)] + def __init__(self, record): + d = record.wave['wave'] + self.name = d['wave_header']['bname'] + self.data = d['wData'] + self.fs = d['wave_header']['fsValid'] + self.fstop = d['wave_header']['topFullScale'] + self.fsbottom = d['wave_header']['botFullScale'] + if record.wave['version'] in [1,2,3]: + dims = [d['wave_header']['npnts']] + [0]*(_MAXDIMS-1) + sfA = [d['wave_header']['hsA']] + [0]*(_MAXDIMS-1) + sfB = [d['wave_header']['hsB']] + [0]*(_MAXDIMS-1) + self.data_units = [d['wave_header']['dataUnits']] + self.axis_units = [d['wave_header']['xUnits']] else: - trimdims = tuple(d for d in dims if d) - dtype = order+ORDER_NUMTYPE[type] - size = int(dtype[2:])*numpy.prod(trimdims) - value = numpy.fromstring(data[pos:pos+size],dtype) - value = value.reshape(trimdims) - - pos = extra_offset - formula = data[pos:pos+formula_size] - pos += formula_size - notes = data[pos:pos+note_size] - pos += note_size - if version == 5: - offset = numpy.cumsum(numpy.hstack((pos,Esize))) - Edata_units = data[offset[0]:offset[1]] - Eaxis_units = [data[offset[i]:offset[i+1]] for i in range(1,5)] - Eaxis_labels = [data[offset[i]:offset[i+1]] for i in range(5,9)] - if Edata_units: data_units = Edata_units - for i,u in enumerate(Eaxis_units): - if u: axis_units[i] = u - axis_labels = Eaxis_labels - pos = offset[-1] - - - self.name = decode(name) - self.data = value - self.data_units = data_units - self.axis_units = axis_units - self.fs,self.fstop,self.fsbottom = fsValid,fsTop,fsBottom - self.axis = [numpy.linspace(a,b,n) - for a,b,n in zip(sf[:4],sf[4:],dims)] - self.formula = formula - self.notes = notes + dims = d['wave_header']['nDim'] + sfA = d['wave_header']['sfA'] + sfB = d['wave_header']['sfB'] + # TODO find example with multiple data units + self.data_units = [d['data_units']] + self.axis_units = [d['dimension_units']] + self.data_units.extend(['']*(_MAXDIMS-len(self.data_units))) + self.data_units = tuple(self.data_units) + self.axis_units.extend(['']*(_MAXDIMS-len(self.axis_units))) + self.axis_units = tuple(self.axis_units) + self.axis = [_numpy.linspace(a,b,c) for a,b,c in zip(sfA, sfB, dims)] + self.formula = d.get('formula', '') + self.notes = d.get('note', '') def format(self, indent=0): if isinstance(self.data, list): type,size = "text", "%d"%len(self.data) @@ -226,14 +124,16 @@ class Recreation(IgorObject): """ Contains the experiment's recreation procedures as plain text. """ - def __init__(self, data, order): self.data = data + def __init__(self, data): + self.data = data def format(self, indent=0): return " "*indent + "" class Procedure(IgorObject): """ Contains the experiment's main procedure window text as plain text. """ - def __init__(self, data, order): self.data = data + def __init__(self, data): + self.data = data def format(self, indent=0): return " "*indent + "" class GetHistory(IgorObject): @@ -245,37 +145,28 @@ class GetHistory(IgorObject): GetHistory entry simply says that the Recreation has run, and the History can be restored from the previously saved value. """ - def __init__(self, data, order): self.data = data + def __init__(self, data): + self.data = data def format(self, indent=0): return " "*indent + "" class PackedFile(IgorObject): """ Contains the data for a procedure file or notebook in packed form. """ - def __init__(self, data, order): self.data = data + def __init__(self, data): + self.data = data def format(self, indent=0): return " "*indent + "" class Unknown(IgorObject): """ Record type not documented in PTN003/TN003. """ - def __init__(self, data, order, type): + def __init__(self, data, type): self.data = data self.type = type def format(self, indent=0): return " "*indent + ""%self.type -class _FolderStart(IgorObject): - """ - Marks the start of a new data folder. - """ - def __init__(self, data, order): - self.name = decode(data[:data.find(chr(0))]) -class _FolderEnd(IgorObject): - """ - Marks the end of a data folder. - """ - def __init__(self, data, order): self.data = data class Folder(IgorObject): """ @@ -320,122 +211,61 @@ def format(self, indent=0): children = [r.format(indent=indent+2) for r in self.children] return u"\n".join([parent]+children) -PARSER = { -1: Variables, -2: History, -3: Wave, -4: Recreation, -5: Procedure, -7: GetHistory, -8: PackedFile, -9: _FolderStart, -10: _FolderEnd, -} - -def loads(s, ignore_unknown=True): + +def loads(s, **kwargs): """Load an igor file from string""" - max = len(s) - pos = 0 - ret = [] + stream = _io.BytesIO(s) + return load(stream, **kwargs) + +def load(filename, **kwargs): + """Load an igor file""" + try: + packed_experiment = _load(filename) + except ValueError as e: + if e.message.startswith('not enough data for the next record header'): + raise IOError('invalid record header; bad pxp file?') + elif e.message.startswith('not enough data for the next record'): + raise IOError('final record too long; bad pxp file?') + raise + return _convert(packed_experiment, **kwargs) + +def _convert(packed_experiment, ignore_unknown=True): + records, filesystem = packed_experiment stack = [Folder(path=[u'root'])] - while pos < max: - if pos+8 > max: - raise IOError("invalid record header; bad pxp file?") - ignore = ord(s[pos])&0x80 - order = '<' if ord(s[pos])&0x77 else '>' - type, version, length = struct.unpack(order+'hhi',s[pos:pos+8]) - pos += 8 - if pos+length > len(s): - raise IOError("final record too long; bad pxp file?") - data = s[pos:pos+length] - pos += length - if not ignore: - parse = PARSER.get(type, None) - if parse: - record = parse(data, order) - elif ignore_unknown: + for record in records: + if isinstance(record, _UnknownRecord): + if ignore_unknown: continue else: - record = Unknown(data=data, order=order, type=type) - if isinstance(record, _FolderStart): - path = stack[-1].path+[record.name] - folder = Folder(path) - stack[-1].append(folder) - stack.append(folder) - elif isinstance(record, _FolderEnd): - stack.pop() - else: - stack[-1].append(record) + r = Unknown(record.data, type=record.header['recordType']) + elif isinstance(record, _GetHistoryRecord): + r = GetHistory(record.text) + elif isinstance(record, _HistoryRecord): + r = History(record.text) + elif isinstance(record, _PackedFileRecord): + r = PackedFile(record.text) + elif isinstance(record, _ProcedureRecord): + r = Procedure(record.text) + elif isinstance(record, _RecreationRecord): + r = Recreation(record.text) + elif isinstance(record, _VariablesRecord): + r = Variables(record) + elif isinstance(record, _WaveRecord): + r = Wave(record) + else: + r = None + + if isinstance(record, _FolderStartRecord): + path = stack[-1].path+[record.null_terminated_text] + folder = Folder(path) + stack[-1].append(folder) + stack.append(folder) + elif isinstance(record, _FolderEndRecord): + stack.pop() + elif r is None: + raise NotImplementedError(record) + else: + stack[-1].append(r) if len(stack) != 1: raise IOError("FolderStart records do not match FolderEnd records") return stack[0] - -def load(filename, ignore_unknown=True): - """Load an igor file""" - return loads(open(filename,'rb').read(), - ignore_unknown=ignore_unknown) - -# ============== Variable parsing ============== -def _parse_sys_numeric(n, order, data, pos): - values = numpy.fromstring(data[pos:pos+n*4], order+'f') - pos += n*4 - var = dict(('K'+str(i),v) for i,v in enumerate(values)) - return var, pos - -def _parse_user_numeric(n, order, data, pos): - var = {} - for i in range(n): - name = data[pos:data.find(chr(0),pos,pos+32)] - type,numtype,real,imag = struct.unpack(order+"hhdd",data[pos+32:pos+52]) - dtype = NUMTYPE[numtype] - if dtype in (numpy.complex64, numpy.complex128): - value = dtype(real+1j*imag) - else: - value = dtype(real) - var[name] = value - pos += 56 - return var, pos - -def _parse_dep_numeric(n, order, data, pos): - var = {} - for i in range(n): - name = data[pos:data.find(chr(0),pos,pos+32)] - type,numtype,real,imag = struct.unpack(order+"hhdd",data[pos+32:pos+52]) - dtype = NUMTYPE[numtype] - if dtype in (numpy.complex64, numpy.complex128): - value = dtype(real+1j*imag) - else: - value = dtype(real) - length, = struct.unpack(order+"h",data[pos+56:pos+58]) - var[name] = Formula(data[pos+58:pos+58+length-1], value) - pos += 58+length - return var, pos - -def _parse_dep_string(n, order, data, pos): - var = {} - for i in range(n): - name = data[pos:data.find(chr(0),pos,pos+32)] - length, = struct.unpack(order+"h",data[pos+48:pos+50]) - var[name] = Formula(data[pos+50:pos+50+length-1], "") - pos += 50+length - return var, pos - -def _parse_user_string1(n, order, data, pos): - var = {} - for i in range(n): - name = data[pos:data.find(chr(0),pos,pos+32)] - length, = struct.unpack(order+"h",data[pos+32:pos+34]) - value = data[pos+34:pos+34+length] - pos += 34+length - var[name] = value - return var, pos - -def _parse_user_string2(n, order, data, pos): - var = {} - for i in range(n): - name = data[pos:data.find(chr(0),pos,pos+32)] - length, = struct.unpack(order+"i",data[pos+32:pos+36]) - value = data[pos+36:pos+36+length] - pos += 36+length - var[name] = value - return var, pos From ffb4b3eb3de75f53a92cbfe16473ab8c1461ea94 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 07:56:49 -0400 Subject: [PATCH 41/76] Avoid expensive pformating in DynamicStructure.unpack_stream unless required. If we're not going to be printing the formatted data in the log, don't bother calculating it. On my system, this speeds up the loading of polar-graphs-demo.pxp by a factor of 10. --- igor/struct.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/igor/struct.py b/igor/struct.py index fce93b5..ea8af34 100644 --- a/igor/struct.py +++ b/igor/struct.py @@ -717,7 +717,8 @@ def unpack_stream(self, stream, parents=None, data=None, d=None): for f in self.fields: _LOG.debug('parsing {!r}.{} (count={}, item_count={})'.format( self, f, f.count, f.item_count)) - _LOG.debug('data:\n{}'.format(_pprint.pformat(data))) + if _LOG.level <= _logging.DEBUG: + _LOG.debug('data:\n{}'.format(_pprint.pformat(data))) if hasattr(f, 'pre_unpack'): _LOG.debug('pre-unpack {}'.format(f)) f.pre_unpack(parents=parents, data=data) From 84d9a567bca5e790ff5e089208a3de383978becd Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 07:59:58 -0400 Subject: [PATCH 42/76] Use DebuggingStream for any LOG.level <= DEBUG (not just ==). --- igor/struct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igor/struct.py b/igor/struct.py index ea8af34..6097c20 100644 --- a/igor/struct.py +++ b/igor/struct.py @@ -709,7 +709,7 @@ def unpack_stream(self, stream, parents=None, data=None, d=None): if data is None: parents = [self] data = d = {} - if _LOG.level == _logging.DEBUG: + if _LOG.level <= _logging.DEBUG: stream = DebuggingStream(stream) else: parents = parents + [self] From 1db460428639f218eaa133326d03d3f105847285 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 08:18:14 -0400 Subject: [PATCH 43/76] Pull common script code into igor.script and add bin/igorpackedexperiment.py. To avoid conflicts with the build-in `-v` `--version` argument, the short form of `--verbose` is now `-V`. --- bin/igorbinarywave.py | 41 +++++++++--------------------------- bin/igorpackedexperiment.py | 32 ++++++++++++++++++++++++++++ igor/script.py | 42 +++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 31 deletions(-) create mode 100755 bin/igorpackedexperiment.py create mode 100644 igor/script.py diff --git a/bin/igorbinarywave.py b/bin/igorbinarywave.py index 9d0fa0e..dc55ceb 100755 --- a/bin/igorbinarywave.py +++ b/bin/igorbinarywave.py @@ -4,42 +4,21 @@ "IBW -> ASCII conversion" -import logging -import optparse import pprint -import sys import numpy -from igor import __version__, LOG from igor.binarywave import load +from igor.script import Script -p = optparse.OptionParser(version=__version__) +def run(args): + wave = load(args.infile) + numpy.savetxt(args.outfile, wave['wave']['wData'], fmt='%g', delimiter='\t') + if args.verbose > 0: + wave['wave'].pop('wData') + pprint.pprint(wave) -p.add_option('-f', '--infile', dest='infile', metavar='FILE', - default='-', help='Input IGOR Binary Wave (.ibw) file.') -p.add_option('-o', '--outfile', dest='outfile', metavar='FILE', - default='-', help='File for ASCII output.') -p.add_option('-v', '--verbose', dest='verbose', default=0, - action='count', help='Increment verbosity') - -options,args = p.parse_args() - -if len(args) > 0 and options.infile == None: - options.infile = args[0] -if options.infile == '-': - options.infile = sys.stdin -if options.outfile == '-': - options.outfile = sys.stdout - -if options.verbose > 1: - log_levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG] - log_level = log_levels[min(options.verbose-1, len(log_levels)-1)] - LOG.setLevel(log_level) - -wave = load(options.infile) -numpy.savetxt(options.outfile, wave['wave']['wData'], fmt='%g', delimiter='\t') -if options.verbose > 0: - wave['wave'].pop('wData') - pprint.pprint(wave) +s = Script(description=__doc__) +s._run = run +s.run() diff --git a/bin/igorpackedexperiment.py b/bin/igorpackedexperiment.py new file mode 100755 index 0000000..056017b --- /dev/null +++ b/bin/igorpackedexperiment.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# +# Copyright + +"PXP -> ASCII conversion" + +import pprint + +import numpy + +from igor.packed import load +from igor.script import Script + + +def run(args): + records,filesystem = load(args.infile) + if hasattr(args.outfile, 'write'): + f = args.outfile # filename is actually a stream object + else: + f = open(args.outfile, 'w') + try: + f.write(pprint.pformat(records)) + f.write('\n') + finally: + if f != args.outfile: + f.close() + if args.verbose > 0: + pprint.pprint(filesystem) + +s = Script(description=__doc__, filetype='IGOR Packed Experiment (.pxp) file') +s._run = run +s.run() diff --git a/igor/script.py b/igor/script.py new file mode 100644 index 0000000..be34b61 --- /dev/null +++ b/igor/script.py @@ -0,0 +1,42 @@ +# Copyright + +"Common code for scripts distributed with the `igor` package." + +from __future__ import absolute_import +import argparse as _argparse +import logging as _logging +import sys as _sys + +from . import __version__ +from . import LOG as _LOG + + +class Script (object): + log_levels = [_logging.ERROR, _logging.WARNING, _logging.INFO, _logging.DEBUG] + + def __init__(self, description=None, filetype='IGOR Binary Wave (.ibw) file'): + self.parser = _argparse.ArgumentParser( + description=description, version=__version__) + self.parser.add_argument( + '-f', '--infile', metavar='FILE', default='-', + help='input {}'.format(filetype)) + self.parser.add_argument( + '-o', '--outfile', metavar='FILE', default='-', + help='file for ASCII output') + self.parser.add_argument( + '-V', '--verbose', action='count', default=0, + help='increment verbosity') + + def run(self, *args, **kwargs): + args = self.parser.parse_args(*args, **kwargs) + if args.infile == '-': + args.infile = _sys.stdin + if args.outfile == '-': + args.outfile = _sys.stdout + if args.verbose > 1: + log_level = self.log_levels[min(args.verbose-1, len(self.log_levels)-1)] + _LOG.setLevel(log_level) + self._run(args) + + def _run(self, args): + raise NotImplementedError() From 9b24573bfc3836f1572e2c1d405f3f6bc749fbc8 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 08:30:08 -0400 Subject: [PATCH 44/76] Ran update-copyright.py. --- bin/igorbinarywave.py | 17 ++++++++++++++++- bin/igorpackedexperiment.py | 17 ++++++++++++++++- igor/__init__.py | 17 ++++++++++++++++- igor/binarywave.py | 25 ++++++++++++------------- igor/packed.py | 17 ++++++++++++++++- igor/record/__init__.py | 17 ++++++++++++++++- igor/record/base.py | 17 ++++++++++++++++- igor/record/folder.py | 17 ++++++++++++++++- igor/record/history.py | 17 ++++++++++++++++- igor/record/packedfile.py | 17 ++++++++++++++++- igor/record/procedure.py | 17 ++++++++++++++++- igor/record/variables.py | 17 ++++++++++++++++- igor/record/wave.py | 17 ++++++++++++++++- igor/script.py | 17 ++++++++++++++++- igor/struct.py | 17 ++++++++++++++++- igor/util.py | 17 ++++++++++++++++- setup.py | 18 +++++++++++++++++- 17 files changed, 269 insertions(+), 29 deletions(-) diff --git a/bin/igorbinarywave.py b/bin/igorbinarywave.py index dc55ceb..6215628 100755 --- a/bin/igorbinarywave.py +++ b/bin/igorbinarywave.py @@ -1,6 +1,21 @@ #!/usr/bin/env python # -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . "IBW -> ASCII conversion" diff --git a/bin/igorpackedexperiment.py b/bin/igorpackedexperiment.py index 056017b..6664a96 100755 --- a/bin/igorpackedexperiment.py +++ b/bin/igorpackedexperiment.py @@ -1,6 +1,21 @@ #!/usr/bin/env python # -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . "PXP -> ASCII conversion" diff --git a/igor/__init__.py b/igor/__init__.py index 816051c..50e057a 100644 --- a/igor/__init__.py +++ b/igor/__init__.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . "Interface for reading binary IGOR files." diff --git a/igor/binarywave.py b/igor/binarywave.py index ee70be2..b172312 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -1,20 +1,19 @@ -# Copyright (C) 2010 W. Trevor King +# Copyright (C) 2010-2012 W. Trevor King # -# This file is part of Hooke. +# This file is part of igor. # -# Hooke is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. # -# Hooke is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -# Public License for more details. +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. # -# You should have received a copy of the GNU Lesser General Public -# License along with Hooke. If not, see -# . +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . "Read IGOR Binary Wave files into Numpy arrays." diff --git a/igor/packed.py b/igor/packed.py index 1b0fec5..c644063 100644 --- a/igor/packed.py +++ b/igor/packed.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . "Read IGOR Packed Experiment files files into records." diff --git a/igor/record/__init__.py b/igor/record/__init__.py index faaa61d..eafebfb 100644 --- a/igor/record/__init__.py +++ b/igor/record/__init__.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . "Record parsers for IGOR's packed experiment files." diff --git a/igor/record/base.py b/igor/record/base.py index f173d20..eebd923 100644 --- a/igor/record/base.py +++ b/igor/record/base.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . class Record (object): diff --git a/igor/record/folder.py b/igor/record/folder.py index be98c58..caaeb54 100644 --- a/igor/record/folder.py +++ b/igor/record/folder.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . from .base import TextRecord diff --git a/igor/record/history.py b/igor/record/history.py index 4d39b75..e5d2199 100644 --- a/igor/record/history.py +++ b/igor/record/history.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . from .base import TextRecord diff --git a/igor/record/packedfile.py b/igor/record/packedfile.py index 9e12437..b457f20 100644 --- a/igor/record/packedfile.py +++ b/igor/record/packedfile.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . from .base import Record diff --git a/igor/record/procedure.py b/igor/record/procedure.py index 59c8610..de00e6e 100644 --- a/igor/record/procedure.py +++ b/igor/record/procedure.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . from .base import TextRecord diff --git a/igor/record/variables.py b/igor/record/variables.py index d604bfd..cdcdd89 100644 --- a/igor/record/variables.py +++ b/igor/record/variables.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . import io as _io diff --git a/igor/record/wave.py b/igor/record/wave.py index db36cfa..49ed20e 100644 --- a/igor/record/wave.py +++ b/igor/record/wave.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . from io import BytesIO as _BytesIO diff --git a/igor/script.py b/igor/script.py index be34b61..cbfd519 100644 --- a/igor/script.py +++ b/igor/script.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . "Common code for scripts distributed with the `igor` package." diff --git a/igor/struct.py b/igor/struct.py index 6097c20..c87a17f 100644 --- a/igor/struct.py +++ b/igor/struct.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . """Structure and Field classes for declaring structures diff --git a/igor/util.py b/igor/util.py index c263cd1..c60e597 100644 --- a/igor/util.py +++ b/igor/util.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . "Utility functions for handling buffers" diff --git a/setup.py b/setup.py index a01a3cf..de684a6 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,20 @@ -# Copyright +# Copyright (C) 2011-2012 Paul Kienzle +# W. Trevor King +# +# This file is part of igor. +# +# igor is free software: you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# igor is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with igor. If not, see . "igor: interface for reading binary IGOR files." From b44e5e86d4646d8bfff5844bdbdcf926e31b7f2e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 08:33:01 -0400 Subject: [PATCH 45/76] Add update-copyright blurb to the README (+minor typo fixes). --- README | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README b/README index bdad784..0166c1e 100644 --- a/README +++ b/README @@ -101,21 +101,26 @@ Maintenance =========== Maintainer -========== +---------- W. Trevor King wking@tremily.us Copyright 2008-2012 Release procedure -================= +----------------- When a new version of the package is ready, increment __version__ -in igor.py and enter:: +in ``igor/__init__.py`` and run update-copyright_:: + + $ update-copyright.py + +to update the copyright blurbs. Then run:: $ python setup.py sdist upload -This will place a new version on pypi. +This will place a new version on PyPI. + .. _layman: http://layman.sourceforge.net/ .. _wtk overlay: http://blog.tremily.us/posts/Gentoo_overlay/ @@ -129,3 +134,4 @@ This will place a new version on pypi. .. _pip: http://pypi.python.org/pypi/pip .. _igor.py: http://pypi.python.org/pypi/igor.py .. _GNU General Public License Version 3: http://www.gnu.org/licenses/gpl.txt +.. _update-copyright: http://blog.tremily.us/posts/update-copyright/ From e82fdde8fa89b8a1bae360106e896051e5547bcd Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 08:40:44 -0400 Subject: [PATCH 46/76] Remove struct._buffer because I don't use it anymore. --- igor/struct.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/igor/struct.py b/igor/struct.py index c87a17f..0d0837e 100644 --- a/igor/struct.py +++ b/igor/struct.py @@ -34,9 +34,6 @@ from . import LOG as _LOG -_buffer = buffer # save builtin buffer for clobbered situations - - class Field (object): """Represent a Structure field. From 446da7b2ef8055eca5a0dadc532579d42b25e4b7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 08:42:10 -0400 Subject: [PATCH 47/76] Don't use u'...' syntax in igor.igorpy. This breaks Python 2 Unicode handling, but it allows igorpy to be used in its raw form by Python 3. --- igor/igorpy.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/igor/igorpy.py b/igor/igorpy.py index 0de34d5..a552edb 100644 --- a/igor/igorpy.py +++ b/igor/igorpy.py @@ -118,7 +118,7 @@ def format(self, indent=0): def __array__(self): return self.data - __repr__ = __str__ = lambda s: u"" % s.format() + __repr__ = __str__ = lambda s: "" % s.format() class Recreation(IgorObject): """ @@ -187,7 +187,7 @@ def __getitem__(self, key): raise KeyError("Folder %s does not exist"%key) def __str__(self): - return u"" % "/".join(self.path) + return "" % "/".join(self.path) __repr__ = __str__ @@ -207,9 +207,9 @@ def append(self, record): pass def format(self, indent=0): - parent = u" "*indent+self.name + parent = " "*indent+self.name children = [r.format(indent=indent+2) for r in self.children] - return u"\n".join([parent]+children) + return "\n".join([parent]+children) def loads(s, **kwargs): @@ -231,7 +231,7 @@ def load(filename, **kwargs): def _convert(packed_experiment, ignore_unknown=True): records, filesystem = packed_experiment - stack = [Folder(path=[u'root'])] + stack = [Folder(path=['root'])] for record in records: if isinstance(record, _UnknownRecord): if ignore_unknown: From 9c9a7bfcb2e4e77ed744e427505ad84f26a3ec98 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 08:48:00 -0400 Subject: [PATCH 48/76] Python 3 scraps the iter(...).next() method, so use next(iter(...)). --- igor/struct.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/igor/struct.py b/igor/struct.py index 0d0837e..4887a14 100644 --- a/igor/struct.py +++ b/igor/struct.py @@ -252,11 +252,11 @@ def unpack_data(self, data): _LOG.debug('unpack {} for {} {}'.format(data, self, self.format)) iterator = iter(data) try: - items = [iterator.next() for i in range(self.arg_count)] + items = [next(iterator) for i in range(self.arg_count)] except StopIteration: raise ValueError('not enough data to unpack {}'.format(self)) try: - iterator.next() + next(iterator) except StopIteration: pass else: @@ -549,13 +549,13 @@ def _unpack_item(self, args): iterator = iter(args) for f in self.fields: try: - items = [iterator.next() for i in range(f.arg_count)] + items = [next(iterator) for i in range(f.arg_count)] except StopIteration: raise ValueError('not enough data to unpack {}.{}'.format( self, f)) data[f.name] = f.unpack_data(items) try: - iterator.next() + next(iterator) except StopIteration: pass else: @@ -671,7 +671,7 @@ class DynamicStructure (Structure): ... ], ... byte_order='>') - >>> b = '\x00\x00\x00\x02\x01\x02\x03\x04' + >>> b = b'\x00\x00\x00\x02\x01\x02\x03\x04' >>> d = dynamic_length_vector.unpack(b) >>> pprint(d) {'data': array([258, 772]), 'length': 2} From 69e98dbe018d80c8106de092ded8f09535607003 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 08:53:42 -0400 Subject: [PATCH 49/76] Use integer division in Field.indexes for Python 3 compatibility. --- igor/struct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igor/struct.py b/igor/struct.py index 4887a14..2885da4 100644 --- a/igor/struct.py +++ b/igor/struct.py @@ -193,7 +193,7 @@ def indexes(self): index = [] for j,c in enumerate(reversed(self.count)): index.insert(0, i % c) - i /= c + i //= c yield index def pack_data(self, data=None): From d2c87ab4ee3f53e5805cb1342b64b24c091bc34e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 09:04:18 -0400 Subject: [PATCH 50/76] Add igor.util._ord for Python 3 compatibility. --- igor/util.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/igor/util.py b/igor/util.py index c60e597..b91bf2b 100644 --- a/igor/util.py +++ b/igor/util.py @@ -22,21 +22,33 @@ import numpy as _numpy +def _ord(byte): + r"""Convert a byte to an integer. + + >>> buffer = b'\x00\x01\x02' + >>> [_ord(b) for b in buffer] + [0, 1, 2] + """ + if _sys.version_info >= (3,): + return byte + else: + return ord(byte) + def hex_bytes(buffer, spaces=None): r"""Pretty-printing for binary buffers. - >>> hex_bytes(buffer('\x00\x01\x02\x03\x04')) + >>> hex_bytes(b'\x00\x01\x02\x03\x04') '0001020304' - >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=1) + >>> hex_bytes(b'\x00\x01\x02\x03\x04', spaces=1) '00 01 02 03 04' - >>> hex_bytes(buffer('\x00\x01\x02\x03\x04'), spaces=2) + >>> hex_bytes(b'\x00\x01\x02\x03\x04', spaces=2) '0001 0203 04' - >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=2) + >>> hex_bytes(b'\x00\x01\x02\x03\x04\x05\x06', spaces=2) '0001 0203 0405 06' - >>> hex_bytes(buffer('\x00\x01\x02\x03\x04\x05\x06'), spaces=3) + >>> hex_bytes(b'\x00\x01\x02\x03\x04\x05\x06', spaces=3) '000102 030405 06' """ - hex_bytes = ['{:02x}'.format(ord(x)) for x in buffer] + hex_bytes = ['{:02x}'.format(_ord(x)) for x in buffer] if spaces is None: return ''.join(hex_bytes) elif spaces is 1: @@ -49,19 +61,19 @@ def assert_null(buffer, strict=True): r"""Ensure an input buffer is entirely zero. >>> import sys - >>> assert_null(buffer('')) - >>> assert_null(buffer('\x00\x00')) - >>> assert_null(buffer('\x00\x01\x02\x03')) + >>> assert_null(b'') + >>> assert_null(b'\x00\x00') + >>> assert_null(b'\x00\x01\x02\x03') Traceback (most recent call last): ... ValueError: 00 01 02 03 >>> stderr = sys.stderr >>> sys.stderr = sys.stdout - >>> assert_null(buffer('\x00\x01\x02\x03'), strict=False) + >>> assert_null(b'\x00\x01\x02\x03', strict=False) warning: post-data padding not zero: 00 01 02 03 >>> sys.stderr = stderr """ - if buffer and ord(max(buffer)) != 0: + if buffer and _ord(max(buffer)) != 0: hex_string = hex_bytes(buffer, spaces=1) if strict: raise ValueError(hex_string) From 93014f2ba67643b9294967c8a4fe1e8d39509013 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 09:07:36 -0400 Subject: [PATCH 51/76] Convert buffer(...) -> bytes(...) for Python 3 compatibility. --- igor/binarywave.py | 4 ++-- igor/packed.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index b172312..ab49cdd 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -82,10 +82,10 @@ def _normalize_string(self, d): if end > start: strings.append(d[start:end]) if self._null_terminated: - strings[-1] = strings[-1].split('\x00', 1)[0] + strings[-1] = strings[-1].split(b'\x00', 1)[0] start = end elif self._null_terminated: - d = d.split('\x00', 1)[0] + d = d.split(b'\x00', 1)[0] return d diff --git a/igor/packed.py b/igor/packed.py index c644063..da773bb 100644 --- a/igor/packed.py +++ b/igor/packed.py @@ -67,7 +67,7 @@ def load(filename, strict=True, ignore_unknown=True): while True: PackedFileRecordHeader.byte_order = initial_byte_order PackedFileRecordHeader.setup() - b = buffer(f.read(PackedFileRecordHeader.size)) + b = bytes(f.read(PackedFileRecordHeader.size)) if not b: break if len(b) < PackedFileRecordHeader.size: @@ -88,7 +88,7 @@ def load(filename, strict=True, ignore_unknown=True): header = PackedFileRecordHeader.unpack_from(b) _LOG.debug( 'reordered version: {}'.format(header['version'])) - data = buffer(f.read(header['numDataBytes'])) + data = bytes(f.read(header['numDataBytes'])) if len(data) < header['numDataBytes']: raise ValueError( ('not enough data for the next record ({} < {})' From 7710836fdb50bf4a604fed7f1d28b82b8ccc99c1 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 09:12:22 -0400 Subject: [PATCH 52/76] Use integer division when calculating DynamicStringIndicesDataField count. In Python 3, the floating point division lead to: Traceback (most recent call last): ... File ".../igor/struct.py", line 255, in unpack_data items = [next(iterator) for i in range(self.arg_count)] TypeError: 'numpy.float64' object cannot be interpreted as an integer --- igor/binarywave.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index ab49cdd..513148b 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -477,7 +477,7 @@ def pre_unpack(self, parents, data): 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 + 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() From 5a636a00a0ce1edc560711bbfaf10726afbcdb25 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 09:37:52 -0400 Subject: [PATCH 53/76] Add W_plrX5.data_units check to test-igorpy.py. --- test/test-igorpy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test-igorpy.py b/test/test-igorpy.py index ab9e9a8..423ac3c 100644 --- a/test/test-igorpy.py +++ b/test/test-igorpy.py @@ -73,6 +73,8 @@ 0.04715481, 0.0467683 , 0.04638178, 0.04599527, 0.04560875, ... 0.00077303, 0.00038651, 0. ]), array([], dtype=float64), array([], dtype=float64), array([], dtype=float64)] +>>> d.W_plrX5.data_units +('', '', '', '') >>> d.W_plrX5.axis_units ('', '', '', '') >>> d.W_plrX5.data # doctest: +ELLIPSIS From 4776df191fd24648ba06aca82725da04dff5eccd Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 09:48:09 -0400 Subject: [PATCH 54/76] Convert exception.message -> exception.args[0] for Python 3 compatibility. Python 3 exceptions no longer have a .message attribute. --- igor/igorpy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/igor/igorpy.py b/igor/igorpy.py index a552edb..ede660f 100644 --- a/igor/igorpy.py +++ b/igor/igorpy.py @@ -16,6 +16,7 @@ from __future__ import absolute_import import io as _io import re as _re +import sys as _sys import numpy as _numpy @@ -222,9 +223,9 @@ def load(filename, **kwargs): try: packed_experiment = _load(filename) except ValueError as e: - if e.message.startswith('not enough data for the next record header'): + if e.args[0].startswith('not enough data for the next record header'): raise IOError('invalid record header; bad pxp file?') - elif e.message.startswith('not enough data for the next record'): + elif e.args[0].startswith('not enough data for the next record'): raise IOError('final record too long; bad pxp file?') raise return _convert(packed_experiment, **kwargs) From 7612a4622392bd4599dc5bbcdd0d6e5b5397e6d8 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 09:54:20 -0400 Subject: [PATCH 55/76] Fixes to string/byte handling for Python 3 compatibility. I don't know if encoding information is embedded in the IGOR files or not. Currently the stock parser just leaves everything it reads in in bytes. For compatibility, the igorpy module attempts to convert those byte strings to Unicode, but it just assumes that the encoding used in the file matches the locale or default encoding used by your system. This could be a portability issue. Until commit commit fe7006e3e2d741b6d80767b1aac53394ff1e7e76 Author: W. Trevor King Date: Sat Jul 21 07:50:09 2012 -0400 Replace igor.igorpy parsing with translations from igor.packed.load. The igorpy parser used sys.getfilesystemencoding() to guess the encoding, but that encoding is actually used to encode file names, not file contents. locale.getpreferredencoding is a better guess, but it's still just a guess. --- igor/binarywave.py | 16 ++++++++++------ igor/igorpy.py | 11 +++++++---- igor/record/base.py | 5 +++-- test/test-igorpy.py | 5 +++-- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index 513148b..c9d1ff9 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -70,10 +70,14 @@ def post_unpack(self, parents, data): wave_data[self.name] = d def _normalize_string(self, d): - if hasattr(d, 'tostring'): + if isinstance(d, bytes): + pass + elif hasattr(d, 'tobytes'): + d = d.tobytes() + elif hasattr(d, 'tostring'): # Python 2 compatibility d = d.tostring() else: - d = ''.join(d) + d = b''.join(d) if self._array_size_field: start = 0 strings = [] @@ -449,7 +453,7 @@ 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 = ''.join(wave_data[self.name]) + d = b''.join(wave_data[self.name]) dim_labels = [] start = 0 for size in bin_header[self._size_field]: @@ -457,7 +461,7 @@ def post_unpack(self, parents, data): if end > start: dim_data = d[start:end] # split null-delimited strings - labels = dim_data.split(chr(0)) + labels = dim_data.split(b'\x00') start = end else: labels = [] @@ -494,10 +498,10 @@ def post_unpack(self, parents, data): for i,offset in enumerate(wave_data['sIndices']): if offset > start: chars = wdata[start:offset] - strings.append(''.join(chars)) + strings.append(b''.join(chars)) start = offset elif offset == start: - strings.append('') + strings.append(b'') else: raise ValueError((offset, wave_data['sIndices'])) wdata = _numpy.array(strings) diff --git a/igor/igorpy.py b/igor/igorpy.py index ede660f..f9e0961 100644 --- a/igor/igorpy.py +++ b/igor/igorpy.py @@ -15,6 +15,7 @@ """ from __future__ import absolute_import import io as _io +import locale as _locale import re as _re import sys as _sys @@ -37,6 +38,7 @@ __version__='0.10' +ENCODING = _locale.getpreferredencoding() or _sys.getdefaultencoding() PYKEYWORDS = set(('and','as','assert','break','class','continue', 'def','elif','else','except','exec','finally', 'for','global','if','import','in','is','lambda', @@ -84,7 +86,7 @@ class Wave(IgorObject): """ def __init__(self, record): d = record.wave['wave'] - self.name = d['wave_header']['bname'] + self.name = d['wave_header']['bname'].decode(ENCODING) self.data = d['wData'] self.fs = d['wave_header']['fsValid'] self.fstop = d['wave_header']['topFullScale'] @@ -100,8 +102,8 @@ def __init__(self, record): sfA = d['wave_header']['sfA'] sfB = d['wave_header']['sfB'] # TODO find example with multiple data units - self.data_units = [d['data_units']] - self.axis_units = [d['dimension_units']] + self.data_units = [d['data_units'].decode(ENCODING)] + self.axis_units = [d['dimension_units'].decode(ENCODING)] self.data_units.extend(['']*(_MAXDIMS-len(self.data_units))) self.data_units = tuple(self.data_units) self.axis_units.extend(['']*(_MAXDIMS-len(self.axis_units))) @@ -257,7 +259,8 @@ def _convert(packed_experiment, ignore_unknown=True): r = None if isinstance(record, _FolderStartRecord): - path = stack[-1].path+[record.null_terminated_text] + path = stack[-1].path + [ + record.null_terminated_text.decode(ENCODING)] folder = Folder(path) stack[-1].append(folder) stack.append(folder) diff --git a/igor/record/base.py b/igor/record/base.py index eebd923..6b168cf 100644 --- a/igor/record/base.py +++ b/igor/record/base.py @@ -42,5 +42,6 @@ class UnusedRecord (Record): class TextRecord (Record): def __init__(self, *args, **kwargs): super(TextRecord, self).__init__(*args, **kwargs) - self.text = str(self.data).replace('\r\n', '\n').replace('\r', '\n') - self.null_terminated_text = self.text.split('\x00', 1)[0] + self.text = bytes(self.data).replace( + b'\r\n', b'\n').replace(b'\r', b'\n') + self.null_terminated_text = self.text.split(b'\x00', 1)[0] diff --git a/test/test-igorpy.py b/test/test-igorpy.py index 423ac3c..1f88927 100644 --- a/test/test-igorpy.py +++ b/test/test-igorpy.py @@ -4,6 +4,7 @@ >>> from pprint import pprint >>> import igor.igorpy as igor +>>> igor.ENCODING = 'UTF-8' Load a packed experiment: @@ -74,9 +75,9 @@ ... 0.00077303, 0.00038651, 0. ]), array([], dtype=float64), array([], dtype=float64), array([], dtype=float64)] >>> d.W_plrX5.data_units -('', '', '', '') +(u'', '', '', '') >>> d.W_plrX5.axis_units -('', '', '', '') +(u'', '', '', '') >>> d.W_plrX5.data # doctest: +ELLIPSIS array([ 1.83690956e-17, 2.69450769e-02, 7.65399113e-02, 1.44305170e-01, 2.23293692e-01, 3.04783821e-01, From 237a5948aed9ffae812b68068355ffd9c3fa8a7b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 10:01:09 -0400 Subject: [PATCH 56/76] Add trove classifiers for Python 2.7 and 3.2 to setup.py. The tests pass on Python 2.7, and all the failures on Python 3.2 are string/bytes display issues. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index de684a6..2862c32 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,8 @@ 'Operating System :: OS Independent', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.2', 'Topic :: Scientific/Engineering', 'Topic :: Software Development :: Libraries :: Python Modules', ], From eba8aab5dccbbbe38bb82791a831b88a70079626 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 10:04:03 -0400 Subject: [PATCH 57/76] Add AUTHORS (generated by update-copyright.py) to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7cf134c..00b2db4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +AUTHORS MANIFEST build/ dist/ From e0a5ed57f0569a41ee72ed81c1242703cc67706e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 11:17:08 -0400 Subject: [PATCH 58/76] Add igor.packed.walk for traversing a packed experiment filesystem. --- igor/packed.py | 11 +++++++++++ igor/util.py | 16 ++++++++++++++++ test/test.py | 25 ++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/igor/packed.py b/igor/packed.py index da773bb..8b5537a 100644 --- a/igor/packed.py +++ b/igor/packed.py @@ -22,6 +22,7 @@ from .struct import Field as _Field from .util import byte_order as _byte_order from .util import need_to_reorder_bytes as _need_to_reorder_bytes +from .util import _bytes from .record import RECORD_TYPE as _RECORD_TYPE from .record.base import UnknownRecord as _UnknownRecord from .record.base import UnusedRecord as _UnusedRecord @@ -181,3 +182,13 @@ def _check_filename(dir_stack, filename): if filename in cwd: raise ValueError('collision on name {} in {}'.format( filename, ':'.join(d for d,cwd in dir_stack))) + +def walk(filesystem, callback, dirpath=None): + """Walk a packed experiment filesystem, operating on each key,value pair. + """ + if dirpath is None: + dirpath = [] + for key,value in sorted((_bytes(k),v) for k,v in filesystem.items()): + callback(dirpath, key, value) + if isinstance(value, dict): + walk(filesystem=value, callback=callback, dirpath=dirpath+[key]) diff --git a/igor/util.py b/igor/util.py index b91bf2b..ecc783a 100644 --- a/igor/util.py +++ b/igor/util.py @@ -110,3 +110,19 @@ def checksum(buffer, byte_order, oldcksum, numbytes): if oldcksum > 2**31: oldcksum -= 2**31 return oldcksum & 0xffff + +def _bytes(obj, encoding='utf-8'): + """Convert bytes or strings into bytes + + >>> _bytes(b'123') + '123' + >>> _bytes('123') + '123' + """ + if _sys.version_info >= (3,): + if isinstance(obj, bytes): + return obj + else: + return bytes(obj, encoding) + else: + return bytes(obj) diff --git a/test/test.py b/test/test.py index 4a76476..1934493 100644 --- a/test/test.py +++ b/test/test.py @@ -1415,6 +1415,21 @@ 'angleQ1': , 'radiusData': , 'radiusQ1': }} + +walking filesystem: +walk callback on ([], root, {'K0': 0.0,...}) +walk callback on (['root'], K0, 0.0) +walk callback on (['root'], K1, 0.0) +walk callback on (['root'], K10, 0.0) +... +walk callback on (['root'], K9, 0.0) +walk callback on (['root'], Packages, {'PolarGraphs': ...}) +walk callback on (['root', 'Packages'], PolarGraphs, {...}) +walk callback on (['root', 'Packages', 'PolarGraphs'], V_bottom, 232.0) +... +walk callback on (['root', 'Packages'], WMDataBase, {...}) +... +walk callback on (['root'], radiusQ1, ) """ import os.path @@ -1423,6 +1438,7 @@ from igor import LOG from igor.binarywave import load as loadibw from igor.packed import load as loadpxp +from igor.packed import walk as _walk from igor.record.base import TextRecord from igor.record.folder import FolderStartRecord, FolderEndRecord from igor.record.variables import VariablesRecord @@ -1438,7 +1454,11 @@ def dumpibw(filename): data = loadibw(path) pprint(data) -def dumppxp(filename): +def walk_callback(dirpath, key, value): + print('walk callback on ({}, {}, {})'.format( + dirpath, key, pformat(value))) + +def dumppxp(filename, walk=True): LOG.info('Testing {}\n'.format(filename)) path = os.path.join(_data_dir, filename) records,filesystem = loadpxp(path) @@ -1456,6 +1476,9 @@ def dumppxp(filename): pprint(record) print('\nfilesystem:') pprint(filesystem) + if walk: + print('\nwalking filesystem:') + _walk(filesystem, walk_callback) def pprint(data): lines = pformat(data).splitlines() From b290d098fee408c7811a1afe507740d70f22f6c4 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 11:30:12 -0400 Subject: [PATCH 59/76] Add -p/--plot option so scripts will plot waves. --- bin/igorbinarywave.py | 21 +++++++++-------- bin/igorpackedexperiment.py | 45 ++++++++++++++++++++++--------------- igor/script.py | 28 +++++++++++++++++++++++ 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/bin/igorbinarywave.py b/bin/igorbinarywave.py index 6215628..70c1bc3 100755 --- a/bin/igorbinarywave.py +++ b/bin/igorbinarywave.py @@ -27,13 +27,16 @@ from igor.script import Script -def run(args): - wave = load(args.infile) - numpy.savetxt(args.outfile, wave['wave']['wData'], fmt='%g', delimiter='\t') - if args.verbose > 0: - wave['wave'].pop('wData') - pprint.pprint(wave) - -s = Script(description=__doc__) -s._run = run +class WaveScript (Script): + def _run(self, args): + wave = load(args.infile) + numpy.savetxt( + args.outfile, wave['wave']['wData'], fmt='%g', delimiter='\t') + self.plot_wave(args, wave) + if args.verbose > 0: + wave['wave'].pop('wData') + pprint.pprint(wave) + + +s = WaveScript(description=__doc__) s.run() diff --git a/bin/igorpackedexperiment.py b/bin/igorpackedexperiment.py index 6664a96..af8bafc 100755 --- a/bin/igorpackedexperiment.py +++ b/bin/igorpackedexperiment.py @@ -23,25 +23,34 @@ import numpy -from igor.packed import load +from igor.packed import load, walk +from igor.record.wave import WaveRecord from igor.script import Script -def run(args): - records,filesystem = load(args.infile) - if hasattr(args.outfile, 'write'): - f = args.outfile # filename is actually a stream object - else: - f = open(args.outfile, 'w') - try: - f.write(pprint.pformat(records)) - f.write('\n') - finally: - if f != args.outfile: - f.close() - if args.verbose > 0: - pprint.pprint(filesystem) - -s = Script(description=__doc__, filetype='IGOR Packed Experiment (.pxp) file') -s._run = run +class PackedScript (Script): + def _run(self, args): + self.args = args + records,filesystem = load(args.infile) + if hasattr(args.outfile, 'write'): + f = args.outfile # filename is actually a stream object + else: + f = open(args.outfile, 'w') + try: + f.write(pprint.pformat(records)) + f.write('\n') + finally: + if f != args.outfile: + f.close() + if args.verbose > 0: + pprint.pprint(filesystem) + walk(filesystem, self._plot_wave_callback) + + def _plot_wave_callback(self, dirpath, key, value): + if isinstance(value, WaveRecord): + self.plot_wave(self.args, value.wave, title=dirpath + [key]) + + +s = PackedScript( + description=__doc__, filetype='IGOR Packed Experiment (.pxp) file') s.run() diff --git a/igor/script.py b/igor/script.py index cbfd519..e14db5e 100644 --- a/igor/script.py +++ b/igor/script.py @@ -22,6 +22,12 @@ import logging as _logging import sys as _sys +try: + import matplotlib as _matplotlib + import matplotlib.pyplot as _matplotlib_pyplot +except ImportError as _matplotlib_import_error: + _matplotlib = None + from . import __version__ from . import LOG as _LOG @@ -38,9 +44,13 @@ def __init__(self, description=None, filetype='IGOR Binary Wave (.ibw) file'): self.parser.add_argument( '-o', '--outfile', metavar='FILE', default='-', help='file for ASCII output') + self.parser.add_argument( + '-p', '--plot', action='store_const', const=True, + help='use Matplotlib to plot any IGOR waves') self.parser.add_argument( '-V', '--verbose', action='count', default=0, help='increment verbosity') + self._num_plots = 0 def run(self, *args, **kwargs): args = self.parser.parse_args(*args, **kwargs) @@ -52,6 +62,24 @@ def run(self, *args, **kwargs): log_level = self.log_levels[min(args.verbose-1, len(self.log_levels)-1)] _LOG.setLevel(log_level) self._run(args) + self.display_plots() def _run(self, args): raise NotImplementedError() + + def plot_wave(self, args, wave, title=None): + if not args.plot: + return # no-op + if not _matplotlib: + raise _matplotlib_import_error + if title is None: + title = wave['wave']['wave_header']['bname'] + figure = _matplotlib_pyplot.figure() + axes = figure.add_subplot(1, 1, 1) + axes.set_title(title) + axes.plot(wave['wave']['wData'], 'r.') + self._num_plots += 1 + + def display_plots(self): + if self._num_plots: + _matplotlib_pyplot.show() From 3ae7034c746b13a46b9e4523e35c45542cf59f32 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 12:20:32 -0400 Subject: [PATCH 60/76] Use an explicit .array attribute to determine if fields are arrays. This improves on the old method of assuming they were scalar if .item_count was 1. --- igor/binarywave.py | 63 ++++++++++++++++++++++------------------ igor/record/variables.py | 9 ++++-- igor/struct.py | 48 ++++++++++++++++++------------ 3 files changed, 70 insertions(+), 50 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index c9d1ff9..bcccda2 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -63,6 +63,11 @@ class StaticStringField (_DynamicField): _null_terminated = False _array_size_field = None + def __init__(self, *args, **kwargs): + if 'array' not in kwargs: + kwargs['array'] = True + super(StaticStringField, self).__init__(*args, **kwargs) + def post_unpack(self, parents, data): wave_structure = parents[-1] wave_data = self._get_structure_data(parents, data, wave_structure) @@ -164,8 +169,8 @@ class NullStaticStringField (StaticStringField): _Field('l', 'formulaSize', help='The size of the dependency formula, if any.'), _Field('l', 'noteSize', help='The size of the note text.'), _Field('l', 'dataEUnitsSize', help='The size of optional extended data units.'), - _Field('l', 'dimEUnitsSize', help='The size of optional extended dimension units.', count=MAXDIMS), - _Field('l', 'dimLabelsSize', help='The size of optional dimension labels.', count=MAXDIMS), + _Field('l', 'dimEUnitsSize', help='The size of optional extended dimension units.', count=MAXDIMS, array=True), + _Field('l', 'dimLabelsSize', help='The size of optional dimension labels.', count=MAXDIMS, array=True), _Field('l', 'sIndicesSize', help='The size of string indicies if this is a text wave.'), _Field('l', 'optionsSize1', default=0, help='Reserved. Write zero. Ignore on read.'), _Field('l', 'optionsSize2', default=0, help='Reserved. Write zero. Ignore on read.'), @@ -191,8 +196,8 @@ class NullStaticStringField (StaticStringField): _Field('h', 'whVersion', default=0, help='Write 0. Ignore on read.'), _Field('h', 'srcFldr', default=0, help='Used in memory only. Write zero. Ignore on read.'), _Field('P', 'fileName', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1), - _Field('c', 'xUnits', default=0, help='Natural x-axis units go here - null if none.', count=MAX_UNIT_CHARS+1), + _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1, array=True), + _Field('c', 'xUnits', default=0, help='Natural x-axis units go here - null if none.', count=MAX_UNIT_CHARS+1, array=True), _Field('l', 'npnts', help='Number of data points in wave.'), _Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), _Field('d', 'hsA', help='X value for point p = hsA*p + hsB'), @@ -207,7 +212,7 @@ class NullStaticStringField (StaticStringField): _Field('P', 'formula', default=0, help='Used in memory only. Write zero. Ignore on read.'), _Field('l', 'depID', default=0, help='Used in memory only. Write zero. Ignore on read.'), _Field('L', 'creationDate', help='DateTime of creation. Not used in version 1 files.'), - _Field('c', 'wUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=2), + _Field('c', 'wUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=2, array=True), _Field('L', 'modDate', help='DateTime of last modification.'), _Field('P', 'waveNoteH', help='Used in memory only. Write zero. Ignore on read.'), ]) @@ -224,27 +229,27 @@ class NullStaticStringField (StaticStringField): _Field('l', 'npnts', help='Total number of points (multiply dimensions up to first zero).'), _Field('h', 'type', help='See types (e.g. NT_FP64) above. Zero for text waves.'), _Field('h', 'dLock', default=0, help='Reserved. Write zero. Ignore on read.'), - _Field('c', 'whpad1', default=0, help='Reserved. Write zero. Ignore on read.', count=6), + _Field('c', 'whpad1', default=0, help='Reserved. Write zero. Ignore on read.', count=6, array=True), _Field('h', 'whVersion', default=1, help='Write 1. Ignore on read.'), NullStaticStringField('c', 'bname', help='Name of wave plus trailing null.', count=MAX_WAVE_NAME5+1), _Field('l', 'whpad2', default=0, help='Reserved. Write zero. Ignore on read.'), _Field('P', 'dFolder', default=0, help='Used in memory only. Write zero. Ignore on read.'), # Dimensioning info. [0] == rows, [1] == cols etc - _Field('l', 'nDim', help='Number of of items in a dimension -- 0 means no data.', count=MAXDIMS), - _Field('d', 'sfA', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS), - _Field('d', 'sfB', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS), + _Field('l', 'nDim', help='Number of of items in a dimension -- 0 means no data.', count=MAXDIMS, array=True), + _Field('d', 'sfA', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS, array=True), + _Field('d', 'sfB', help='Index value for element e of dimension d = sfA[d]*e + sfB[d].', count=MAXDIMS, array=True), # SI units - _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1), - _Field('c', 'dimUnits', default=0, help='Natural dimension units go here - null if none.', count=(MAXDIMS, MAX_UNIT_CHARS+1)), + _Field('c', 'dataUnits', default=0, help='Natural data units go here - null if none.', count=MAX_UNIT_CHARS+1, array=True), + _Field('c', 'dimUnits', default=0, help='Natural dimension units go here - null if none.', count=(MAXDIMS, MAX_UNIT_CHARS+1), array=True), _Field('h', 'fsValid', help='TRUE if full scale values have meaning.'), _Field('h', 'whpad3', default=0, help='Reserved. Write zero. Ignore on read.'), _Field('d', 'topFullScale', help='The max and max full scale value for wave'), # sic, probably "max and min" _Field('d', 'botFullScale', help='The max and max full scale value for wave.'), # sic, probably "max and min" _Field('P', 'dataEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('P', 'dimEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS), - _Field('P', 'dimLabels', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS), + _Field('P', 'dimEUnits', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS, array=True), + _Field('P', 'dimLabels', default=0, help='Used in memory only. Write zero. Ignore on read.', count=MAXDIMS, array=True), _Field('P', 'waveNoteH', default=0, help='Used in memory only. Write zero. Ignore on read.'), - _Field('l', 'whUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=16), + _Field('l', 'whUnused', default=0, help='Reserved. Write zero. Ignore on read.', count=16, array=True), # The following stuff is considered private to Igor. _Field('h', 'aModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), _Field('h', 'wModified', default=0, help='Used in memory only. Write zero. Ignore on read.'), @@ -577,7 +582,7 @@ def post_unpack(self, parents, data): fields=[ _Field(BinHeader1, 'bin_header', help='Binary wave header'), _Field(WaveHeader2, 'wave_header', help='Wave header'), - DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0), + DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0, array=True), ]) Wave2 = _DynamicStructure( @@ -585,9 +590,9 @@ def post_unpack(self, parents, data): fields=[ _Field(BinHeader2, 'bin_header', help='Binary wave header'), _Field(WaveHeader2, 'wave_header', help='Wave header'), - DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0), - _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16), - DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0), + DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0, array=True), + _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16, array=True), + DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0, array=True), ]) Wave3 = _DynamicStructure( @@ -595,10 +600,10 @@ def post_unpack(self, parents, data): fields=[ _Field(BinHeader3, 'bin_header', help='Binary wave header'), _Field(WaveHeader2, 'wave_header', help='Wave header'), - DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0), - _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16), - DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0), - DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula', count=0), + DynamicWaveDataField1('f', 'wData', help='The start of the array of waveform data.', count=0, array=True), + _Field('x', 'padding', help='16 bytes of padding in versions 2 and 3.', count=16, array=True), + DynamicWaveNoteField('c', 'note', help='Optional wave note data', count=0, array=True), + DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula', count=0, array=True), ]) Wave5 = _DynamicStructure( @@ -606,13 +611,13 @@ def post_unpack(self, parents, data): fields=[ _Field(BinHeader5, 'bin_header', help='Binary wave header'), _Field(WaveHeader5, 'wave_header', help='Wave header'), - DynamicWaveDataField5('f', 'wData', help='The start of the array of waveform data.', count=0), - DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula.', count=0), - DynamicWaveNoteField('c', 'note', help='Optional wave note data.', count=0), - DynamicDataUnitsField('c', 'data_units', help='Optional extended data units data.', count=0), - DynamicDimensionUnitsField('c', 'dimension_units', help='Optional dimension label data', count=0), - DynamicLabelsField('c', 'labels', help="Optional dimension label data", count=0), - DynamicStringIndicesDataField('P', 'sIndices', help='Dynamic string indices for text waves.', count=0), + DynamicWaveDataField5('f', 'wData', help='The start of the array of waveform data.', count=0, array=True), + DynamicDependencyFormulaField('c', 'formula', help='Optional wave dependency formula.', count=0, array=True), + DynamicWaveNoteField('c', 'note', help='Optional wave note data.', count=0, array=True), + DynamicDataUnitsField('c', 'data_units', help='Optional extended data units data.', count=0, array=True), + DynamicDimensionUnitsField('c', 'dimension_units', help='Optional dimension label data', count=0, array=True), + DynamicLabelsField('c', 'labels', help="Optional dimension label data", count=0, array=True), + DynamicStringIndicesDataField('P', 'sIndices', help='Dynamic string indices for text waves.', count=0, array=True), ]) Wave = _DynamicStructure( diff --git a/igor/record/variables.py b/igor/record/variables.py index cdcdd89..a8eaccf 100644 --- a/igor/record/variables.py +++ b/igor/record/variables.py @@ -75,6 +75,11 @@ def _get_size_data(self, parents, data): class DynamicVarDataField (_DynamicField): + def __init__(self, *args, **kwargs): + if 'array' not in kwargs: + kwargs['array'] = True + super(DynamicVarDataField, self).__init__(*args, **kwargs) + def pre_pack(self, parents, data): raise NotImplementedError() @@ -247,8 +252,8 @@ def post_unpack(self, parents, data): DynamicSysVarField('f', 'sysVars', help='System variables', count=0), DynamicUserVarField(UserNumVarRec, 'userVars', help='User numeric variables', count=0), DynamicUserStrField(UserStrVarRec2, 'userStrs', help='User string variables', count=0), - _Field(UserDependentVarRec, 'dependentVars', help='Dependent numeric variables.', count=0), - _Field(UserDependentVarRec, 'dependentStrs', help='Dependent string variables.', count=0), + _Field(UserDependentVarRec, 'dependentVars', help='Dependent numeric variables.', count=0, array=True), + _Field(UserDependentVarRec, 'dependentStrs', help='Dependent string variables.', count=0, array=True), ]) diff --git a/igor/struct.py b/igor/struct.py index 2885da4..6a19e2c 100644 --- a/igor/struct.py +++ b/igor/struct.py @@ -65,7 +65,7 @@ class Field (object): Example of a multi-dimensional float field: >>> data = Field( - ... 'f', 'data', help='example data', count=(2,3,4)) + ... 'f', 'data', help='example data', count=(2,3,4), array=True) >>> data.arg_count 24 >>> list(data.indexes()) # doctest: +ELLIPSIS @@ -91,7 +91,7 @@ class Field (object): Example of a nested structure field: >>> run = Structure('run', fields=[time, data]) - >>> runs = Field(run, 'runs', help='pair of runs', count=2) + >>> runs = Field(run, 'runs', help='pair of runs', count=2, array=True) >>> runs.arg_count # = 2 * (1 + 24) 50 >>> data1 = numpy.arange(data.arg_count).reshape(data.count) @@ -148,12 +148,14 @@ class Field (object): -------- Structure """ - def __init__(self, format, name, default=None, help=None, count=1): + def __init__(self, format, name, default=None, help=None, count=1, + array=False): self.format = format self.name = name self.default = default self.help = help self.count = count + self.array = array self.setup() def setup(self): @@ -164,6 +166,10 @@ def setup(self): """ _LOG.debug('setup {}'.format(self)) self.item_count = _numpy.prod(self.count) # number of item repeats + if not self.array and self.item_count != 1: + raise ValueError( + '{} must be an array field to have a count of {}'.format( + self, self.count)) if isinstance(self.format, Structure): self.structure_count = sum( f.arg_count for f in self.format.fields) @@ -182,7 +188,7 @@ def __repr__(self): def indexes(self): """Iterate through indexes to a possibly multi-dimensional array""" - assert self.item_count > 1, self + assert self.array, self try: i = [0] * len(self.count) except TypeError: # non-iterable count @@ -202,7 +208,7 @@ def pack_data(self, data=None): If the field is repeated (count > 1), the incoming data should be iterable with each iteration returning a single item. """ - if self.item_count > 1: + if self.array: if data is None: data = [] if hasattr(data, 'flat'): # take advantage of numpy's ndarray.flat @@ -230,7 +236,7 @@ def pack_data(self, data=None): item = None for arg in self.pack_item(item): yield arg - elif self.item_count: + else: for arg in self.pack_item(data): yield arg @@ -272,7 +278,8 @@ def unpack_data(self, data): count = self.count else: count = 0 # padding bytes, etc. - if count == 1: + if not self.array: + assert count == 1, (self, self.count) return unpacked[0] if isinstance(self.format, Structure): try: @@ -377,11 +384,12 @@ class Structure (_struct.Struct): >>> time = Field('I', 'time', default=0, help='POSIX time') >>> data = Field( - ... 'h', 'data', default=0, help='example data', count=(2,3)) + ... 'h', 'data', default=0, help='example data', count=(2,3), + ... array=True) >>> run = Structure('run', fields=[time, data]) >>> version = Field( ... 'H', 'version', default=1, help='example version') - >>> runs = Field(run, 'runs', help='pair of runs', count=2) + >>> runs = Field(run, 'runs', help='pair of runs', count=2, array=True) >>> experiment = Structure('experiment', fields=[version, runs]) The structures automatically calculate the flattened data format: @@ -453,7 +461,8 @@ class Structure (_struct.Struct): If you set ``count=0``, the field is ignored. >>> experiment2 = Structure('experiment', fields=[ - ... version, Field('f', 'ignored', count=0), runs], byte_order='>') + ... version, Field('f', 'ignored', count=0, array=True), runs], + ... byte_order='>') >>> experiment2.format '>HIhhhhhhIhhhhhh' >>> d = experiment2.unpack(b) @@ -656,7 +665,7 @@ class DynamicStructure (Structure): >>> dynamic_length_vector = DynamicStructure('vector', ... fields=[ ... DynamicLengthField('I', 'length'), - ... Field('h', 'data', count=0), + ... Field('h', 'data', count=0, array=True), ... ], ... byte_order='>') >>> class DynamicDataField (DynamicField): @@ -667,7 +676,7 @@ class DynamicStructure (Structure): >>> dynamic_data_vector = DynamicStructure('vector', ... fields=[ ... Field('I', 'length'), - ... DynamicDataField('h', 'data', count=0), + ... DynamicDataField('h', 'data', count=0, array=True), ... ], ... byte_order='>') @@ -746,18 +755,18 @@ def unpack_stream(self, stream, parents=None, data=None, d=None): f.setup() f.format.setup() if isinstance(f.format, DynamicStructure): - if f.item_count == 1: - # TODO, fix in case we *want* an array - d[f.name] = {} - f.format.unpack_stream( - stream, parents=parents, data=data, d=d[f.name]) - else: + if f.array: d[f.name] = [] for i in range(f.item_count): x = {} d[f.name].append(x) f.format.unpack_stream( stream, parents=parents, data=data, d=x) + else: + assert f.item_count == 1, (f, f.count) + d[f.name] = {} + f.format.unpack_stream( + stream, parents=parents, data=data, d=d[f.name]) if hasattr(f, 'post_unpack'): _LOG.debug('post-unpack {}'.format(f)) repeat = f.post_unpack(parents=parents, data=data) @@ -774,7 +783,8 @@ def unpack(): f.setup() f.format.setup() x = [f.format.unpack_from(b) for b in bs] - if len(x) == 1: # TODO, fix in case we *want* an array + if not f.array: + assert len(x) == 1, (f, f.count, x) x = x[0] return x else: From dadce20a9b0dc33cac556321f0b5f761b64bbb21 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 12:24:50 -0400 Subject: [PATCH 61/76] Carry on plotting in scripts if one of the plots fails (e.g. for text waves). --- igor/script.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/igor/script.py b/igor/script.py index e14db5e..acb1a22 100644 --- a/igor/script.py +++ b/igor/script.py @@ -77,7 +77,11 @@ def plot_wave(self, args, wave, title=None): figure = _matplotlib_pyplot.figure() axes = figure.add_subplot(1, 1, 1) axes.set_title(title) - axes.plot(wave['wave']['wData'], 'r.') + try: + axes.plot(wave['wave']['wData'], 'r.') + except ValueError as error: + _LOG.error('error plotting {}: {}'.format(title, error)) + pass self._num_plots += 1 def display_plots(self): From c6ffafdedea3539cdb7596670f34376f14ddec46 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 15:21:37 -0400 Subject: [PATCH 62/76] Add an alias from gmail -> nist for Paul Kienzle to .mailmap. --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 4a905eb..c591e2a 100644 --- a/.mailmap +++ b/.mailmap @@ -1 +1,2 @@ W. Trevor King +Paul Kienzle From b81f29eab02abfac6b0d0ffe86c614e8cc016f1c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 21 Jul 2012 15:39:21 -0400 Subject: [PATCH 63/76] Add closing semicolons (;) to C structures in docstrings. --- igor/struct.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/igor/struct.py b/igor/struct.py index 6a19e2c..a50ede5 100644 --- a/igor/struct.py +++ b/igor/struct.py @@ -373,12 +373,12 @@ class Structure (_struct.Struct): struct run { unsigned int time; short data[2][3]; - } + }; struct experiment { unsigned short version; struct run runs[2]; - } + }; As: @@ -632,7 +632,7 @@ class DynamicStructure (Structure): struct vector { unsigned int length; short data[length]; - } + }; You can generate a Python version of this structure in two ways, with a dynamic ``length``, or with a dynamic ``data``. In both From b6d4db050112d601e74b57ba3598bf15d438f369 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 22 Jul 2012 10:32:09 -0400 Subject: [PATCH 64/76] Fix homepage url formatting (%s -> {}) in setup.py. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2862c32..b08926a 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ version=__version__, maintainer='W. Trevor King', maintainer_email='wking@tremily.us', - url='http://blog.tremily.us/posts/%s/'.format(package_name), + url='http://blog.tremily.us/posts/{}/'.format(package_name), download_url='http://git.tremily.us/?p={}.git;a=snapshot;h=v{};sf=tgz'.format(package_name, __version__), license='GNU General Public License (GPL)', platforms=['all'], From ff3da757b3993c4160e0de53380e858e47301078 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 22 Jul 2012 11:29:47 -0400 Subject: [PATCH 65/76] Add igorpackedexperiment.py to setup.py script list. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index b08926a..67eb254 100644 --- a/setup.py +++ b/setup.py @@ -56,6 +56,7 @@ ], scripts=[ 'bin/igorbinarywave.py', + 'bin/igorpackedexperiment.py', ], provides=['igor (%s)' % __version__], ) From 53027c19757217948409f5f9702975ebaaca961a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 22 Jul 2012 11:30:40 -0400 Subject: [PATCH 66/76] Use modern {} formatting instead of %s for setup.py's provides option. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 67eb254..a204c0e 100644 --- a/setup.py +++ b/setup.py @@ -58,5 +58,5 @@ 'bin/igorbinarywave.py', 'bin/igorpackedexperiment.py', ], - provides=['igor (%s)' % __version__], + provides=['igor ({})'.format(__version__)], ) From 1a740e916c925a10e7b43ae13470bc21b5cce290 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 24 Jul 2012 12:04:12 -0400 Subject: [PATCH 67/76] igorpackedexperiment.py doesn't use numpy, so don't import it. --- bin/igorpackedexperiment.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/igorpackedexperiment.py b/bin/igorpackedexperiment.py index af8bafc..0a08444 100755 --- a/bin/igorpackedexperiment.py +++ b/bin/igorpackedexperiment.py @@ -21,8 +21,6 @@ import pprint -import numpy - from igor.packed import load, walk from igor.record.wave import WaveRecord from igor.script import Script From 75ddaa018c55ef748898e9483240ab6f92a1bea3 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 26 Jul 2012 14:23:23 -0400 Subject: [PATCH 68/76] Clear up copyright ambiguity by changing to LGPLv3+. On Thu, Jul 26, 2012 at 09:25:20AM -0700, Paul Kienzle wrote: > Your .py files are licensed as LGPL but the overall project is > listed as GPL. Much of the python numerics world is BSD licensed, > so LGPL is a closer match to the community. Do you mind changing > the project to LGPL? --- COPYING.LESSER | 165 +++++++++++++++++++++++++++++++++++++++++++++++++ MANIFEST.in | 1 + README | 9 +-- setup.py | 4 +- 4 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 COPYING.LESSER diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 0000000..fc8a5de --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/MANIFEST.in b/MANIFEST.in index eb762f3..f6725a8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,2 @@ include COPYING +include COPYING.LESSER diff --git a/README b/README index 0166c1e..82a4ca5 100644 --- a/README +++ b/README @@ -93,9 +93,9 @@ you'll have to clone the Git repository or download a snapshot. Licence ======= -This project is distributed under the `GNU General Public License -Version 3`_ or greater, see the ``COPYING`` file distributed with the -project for details. +This project is distributed under the `GNU Lesser General Public +License Version 3`_ or greater, see the ``COPYING`` file distributed +with the project for details. Maintenance =========== @@ -133,5 +133,6 @@ This will place a new version on PyPI. .. _homepage: http://blog.tremily.us/posts/igor/ .. _pip: http://pypi.python.org/pypi/pip .. _igor.py: http://pypi.python.org/pypi/igor.py -.. _GNU General Public License Version 3: http://www.gnu.org/licenses/gpl.txt +.. _GNU Lesser General Public License Version 3: + http://www.gnu.org/licenses/lgpl.txt .. _update-copyright: http://blog.tremily.us/posts/update-copyright/ diff --git a/setup.py b/setup.py index a204c0e..2ff3273 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ maintainer_email='wking@tremily.us', url='http://blog.tremily.us/posts/{}/'.format(package_name), download_url='http://git.tremily.us/?p={}.git;a=snapshot;h=v{};sf=tgz'.format(package_name, __version__), - license='GNU General Public License (GPL)', + license='GNU Lesser General Public License v3 or later (LGPLv3+)', platforms=['all'], description=__doc__, long_description=open(os.path.join(_this_dir, 'README'), 'r').read(), @@ -43,7 +43,7 @@ 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'Operating System :: OS Independent', - 'License :: OSI Approved :: GNU General Public License (GPL)', + 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.2', From 551d9bca2f161511b1b09afdbeabff86284cb371 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 20 Aug 2012 14:01:36 -0400 Subject: [PATCH 69/76] Fix DynamicLabelsField parsing algorithm. The old algorithm collapsed null bytes early on. The new algorithm keeps the null bytes until the 32-byte chunks have been read. Parsing hooke/test/data/vclamp_mfp3d/Line0004Point0001.ibw with the old algorithm gave labels as [[], ['RawDeflLVDT'], [], []] The new algorithm gives [[], ['', 'Raw', 'Defl', 'LVDT'], [], []] --- igor/binarywave.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/igor/binarywave.py b/igor/binarywave.py index bcccda2..6d87d14 100644 --- a/igor/binarywave.py +++ b/igor/binarywave.py @@ -458,15 +458,22 @@ 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 = b''.join(wave_data[self.name]) + 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] - # split null-delimited strings - labels = dim_data.split(b'\x00') + 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 = [] From 748cddf4fe2c85bc5a30a08af57c54b0a6834892 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 11 Oct 2012 07:33:49 -0400 Subject: [PATCH 70/76] README: fix reStructuredText for `pip install` example. --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 82a4ca5..dc21b62 100644 --- a/README +++ b/README @@ -54,7 +54,7 @@ standard:: $ python setup.py install -You can also automate this installation with pip_. +You can also automate this installation with pip_:: $ pip install igor From 2c2a79d85508c8988b6d4ecfd4d0f55cff35ef11 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 8 Apr 2015 10:19:18 -0700 Subject: [PATCH 71/76] test/test.py: Fix ['Column0'] -> ['', 'Column0'] Catch up with 551d9bca (Fix DynamicLabelsField parsing algorithm., 2012-08-20). --- test/test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test.py b/test/test.py index 1934493..841cf45 100644 --- a/test/test.py +++ b/test/test.py @@ -196,7 +196,7 @@ 'data_units': '', 'dimension_units': '', 'formula': '', - 'labels': [['Column0'], [], [], []], + 'labels': [['', 'Column0'], [], [], []], 'note': 'This is a test.', 'sIndices': array([], dtype=float64), 'wData': array([ 5., 4., 3., 2., 1.], dtype=float32), @@ -459,7 +459,7 @@ 'data_units': '', 'dimension_units': '', 'formula': '', - 'labels': [['Column0'], [], [], []], + 'labels': [['', 'Column0'], [], [], []], 'note': 'This is a test.', 'sIndices': array([], dtype=float64), 'wData': array([ 5., 4., 3., 2., 1.], dtype=float32), From 2b2b425de4fe919e05018f773323e76472e4c737 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 1 Aug 2016 21:02:50 -0700 Subject: [PATCH 72/76] igor.script: Replace ArgumentParser(version=...) with a version argument ArgumentParser lost its undocumented version argument in 3.3.0 [1,2,3]. The version action is the documented way to do this [4]. [1]: http://bugs.python.org/issue13248 [2]: https://hg.python.org/cpython/rev/5393382c1b1d [3]: https://hg.python.org/cpython/file/374f501f4567/Misc/HISTORY#l477 [4]: https://docs.python.org/3/library/argparse.html#action --- igor/script.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/igor/script.py b/igor/script.py index acb1a22..685fdd1 100644 --- a/igor/script.py +++ b/igor/script.py @@ -36,8 +36,10 @@ class Script (object): log_levels = [_logging.ERROR, _logging.WARNING, _logging.INFO, _logging.DEBUG] def __init__(self, description=None, filetype='IGOR Binary Wave (.ibw) file'): - self.parser = _argparse.ArgumentParser( - description=description, version=__version__) + self.parser = _argparse.ArgumentParser(description=description) + self.parser.add_argument( + '--version', action='version', + version='%(prog)s {}'.format(__version__)) self.parser.add_argument( '-f', '--infile', metavar='FILE', default='-', help='input {}'.format(filetype)) From d792fd52667563a306efc1dcc8d6e392043a6b2d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 1 Aug 2016 21:24:52 -0700 Subject: [PATCH 73/76] setup.py: Claim compatibility through 3.5 I haven't actually tested all of these, but I doubt I did anything so magical that support has been dropped in the meantime ;). It would be nice to drop the doctests [1], but until then testing Python 3 is going to be difficult. [1]: https://github.com/wking/igor/pull/1#issuecomment-190309065 --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 2ff3273..8d0813b 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,9 @@ 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Scientific/Engineering', 'Topic :: Software Development :: Libraries :: Python Modules', ], From f3733eb217f719aabba8fcfc520b110d76170731 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 1 Aug 2016 20:53:05 -0700 Subject: [PATCH 74/76] .update-copyright.conf: Update configuration for update-copyright 0.6.2 --- .update-copyright.conf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.update-copyright.conf b/.update-copyright.conf index 9ce16c9..d5ffe5e 100644 --- a/.update-copyright.conf +++ b/.update-copyright.conf @@ -5,14 +5,14 @@ vcs: Git [files] authors: yes files: yes -ignored: COPYING, README, .update-copyright.conf, .git*, test/* +ignored: COPYING | README | .update-copyright.conf | .git* | test/* [copyright] -short: %(project)s comes with ABSOLUTELY NO WARRANTY and is licensed under the GNU Lesser General Public License. For details, %%(get-details)s. -long: This file is part of %(project)s. +short: {project} comes with ABSOLUTELY NO WARRANTY and is licensed under the GNU Lesser General Public License. +long: This file is part of {project}. - %(project)s is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + {project} is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - %(project)s is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + {project} is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public License along with %(project)s. If not, see . + You should have received a copy of the GNU Lesser General Public License along with {project}. If not, see . From 0a43edc7315fdf9c6d6e8c9592cb09788271d1a0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 1 Aug 2016 20:53:39 -0700 Subject: [PATCH 75/76] Run update-copyright.py --- igor/__init__.py | 2 +- igor/script.py | 2 +- setup.py | 2 +- test/test-igorpy.py | 17 ++++++++++++++++- test/test.py | 17 ++++++++++++++++- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/igor/__init__.py b/igor/__init__.py index 50e057a..22c358f 100644 --- a/igor/__init__.py +++ b/igor/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012 W. Trevor King +# Copyright (C) 2012-2016 W. Trevor King # # This file is part of igor. # diff --git a/igor/script.py b/igor/script.py index 685fdd1..83fde93 100644 --- a/igor/script.py +++ b/igor/script.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012 W. Trevor King +# Copyright (C) 2012-2016 W. Trevor King # # This file is part of igor. # diff --git a/setup.py b/setup.py index 8d0813b..2c9ff77 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2012 Paul Kienzle +# Copyright (C) 2011-2016 Paul Kienzle # W. Trevor King # # This file is part of igor. diff --git a/test/test-igorpy.py b/test/test-igorpy.py index 1f88927..c419311 100644 --- a/test/test-igorpy.py +++ b/test/test-igorpy.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012 W. Trevor King +# +# This file is part of %(project)s. +# +# %(project)s is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# %(project)s is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with %(project)s. If not, see . r"""Test the igor.igorpy compatibility layer by loading sample files. diff --git a/test/test.py b/test/test.py index 841cf45..1b40812 100644 --- a/test/test.py +++ b/test/test.py @@ -1,4 +1,19 @@ -# Copyright +# Copyright (C) 2012-2015 W. Trevor King +# +# This file is part of %(project)s. +# +# %(project)s is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# %(project)s is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with %(project)s. If not, see . r"""Test the igor module by loading sample files. From 41952c5fa352fdbe95557a759cdc057fe3ee74f9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 1 Aug 2016 20:54:46 -0700 Subject: [PATCH 76/76] igor: Bump to 0.3 Changes since 0.2: * Add igor.packed.walk for traversing a packed experiment filesystem. * Add igorpackedexperiment.py to setup.py script list. * Add -p/--plot option so scripts will plot waves. * Adjust copyright to LGPLv3+ * Assorted bugfixes --- igor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/igor/__init__.py b/igor/__init__.py index 22c358f..213e9ab 100644 --- a/igor/__init__.py +++ b/igor/__init__.py @@ -17,7 +17,7 @@ "Interface for reading binary IGOR files." -__version__ = '0.2' +__version__ = '0.3' import logging as _logging