Commit d400cacf authored by W. Trevor King's avatar W. Trevor King
Browse files

Split record handling into modules and implement VariablesRecord.

parent cf045181
# 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()
......
# 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,
}
# 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
# Copyright
from .base import Record
class FolderStartRecord (Record):
pass
class FolderEndRecord (Record):
pass
# Copyright
from .base import Record
class GetHistoryRecord (Record):
pass
# Copyright
from .base import Record
class HistoryRecord (Record):
pass
# Copyright
from .base import Record
class PackedFileRecord (Record):
pass
# Copyright
from .base import Record
class ProcedureRecord (Record):
pass
# Copyright
from .base import Record
class RecreationRecord (Record):
pass
# 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'])
# 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)
......@@ -32,6 +32,7 @@ setup(name=package_name,
],
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
......@@ -652,7 +654,15 @@ record 28:
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:
......@@ -1242,13 +1252,70 @@ record 40:
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:
......@@ -1267,7 +1334,8 @@ import sys
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)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment