# 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
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()
--- /dev/null
+# 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,
+ }
--- /dev/null
+# 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
--- /dev/null
+# Copyright
+
+from .base import Record
+
+
+class FolderStartRecord (Record):
+ pass
+
+
+class FolderEndRecord (Record):
+ pass
--- /dev/null
+# Copyright
+
+from .base import Record
+
+
+class GetHistoryRecord (Record):
+ pass
--- /dev/null
+# Copyright
+
+from .base import Record
+
+
+class HistoryRecord (Record):
+ pass
--- /dev/null
+# Copyright
+
+from .base import Record
+
+
+class PackedFileRecord (Record):
+ pass
--- /dev/null
+# Copyright
+
+from .base import Record
+
+
+class ProcedureRecord (Record):
+ pass
--- /dev/null
+# Copyright
+
+from .base import Record
+
+
+class RecreationRecord (Record):
+ pass
--- /dev/null
+# 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'])
--- /dev/null
+# 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)
],
packages=[
'igor',
+ 'igor.record',
],
scripts=[
'bin/igorbinarywave.py',
+# Copyright
+
r"""Test the igor module by loading sample files.
>>> dumpibw('mac-double.ibw', strict=False) # doctest: +REPORT_UDIFF
record 29:
<UnknownRecord-26 ...>
record 30:
-<VariablesRecord ...>
+{'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:
<HistoryRecord ...>
record 32:
record 41:
<FolderStartRecord ...>
record 42:
-<VariablesRecord ...>
+{'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:
<FolderEndRecord ...>
record 44:
<FolderStartRecord ...>
record 45:
-<VariablesRecord ...>
+{'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:
<FolderEndRecord ...>
record 47:
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__)
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)