1 # Copyright (C) 2012 W. Trevor King <wking@tremily.us>
3 # This file is part of igor.
5 # igor is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU Lesser General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option) any
10 # igor is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with igor. If not, see <http://www.gnu.org/licenses/>.
18 "Read IGOR Packed Experiment files files into records."
20 from . import LOG as _LOG
21 from .struct import Structure as _Structure
22 from .struct import Field as _Field
23 from .util import byte_order as _byte_order
24 from .util import need_to_reorder_bytes as _need_to_reorder_bytes
25 from .record import RECORD_TYPE as _RECORD_TYPE
26 from .record.base import UnknownRecord as _UnknownRecord
27 from .record.base import UnusedRecord as _UnusedRecord
28 from .record.folder import FolderStartRecord as _FolderStartRecord
29 from .record.folder import FolderEndRecord as _FolderEndRecord
30 from .record.variables import VariablesRecord as _VariablesRecord
31 from .record.wave import WaveRecord as _WaveRecord
35 # Igor writes other kinds of records in a packed experiment file, for
36 # storing things like pictures, page setup records, and miscellaneous
37 # settings. The format for these records is quite complex and is not
38 # described in PTN003. If you are writing a program to read packed
39 # files, you must skip any record with a record type that is not
42 PackedFileRecordHeader = _Structure(
43 name='PackedFileRecordHeader',
45 _Field('H', 'recordType', help='Record type plus superceded flag.'),
46 _Field('h', 'version', help='Version information depends on the type of record.'),
47 _Field('l', 'numDataBytes', help='Number of data bytes in the record following this record header.'),
52 PACKEDRECTYPE_MASK = 0x7FFF # Record type = (recordType & PACKEDREC_TYPE_MASK)
53 SUPERCEDED_MASK = 0x8000 # Bit is set if the record is superceded by
54 # a later record in the packed file.
57 def load(filename, strict=True, ignore_unknown=True):
58 _LOG.debug('loading a packed experiment file from {}'.format(filename))
60 if hasattr(filename, 'read'):
61 f = filename # filename is actually a stream object
63 f = open(filename, 'rb')
65 initial_byte_order = '='
68 PackedFileRecordHeader.byte_order = initial_byte_order
69 PackedFileRecordHeader.setup()
70 b = bytes(f.read(PackedFileRecordHeader.size))
73 if len(b) < PackedFileRecordHeader.size:
75 ('not enough data for the next record header ({} < {})'
76 ).format(len(b), PackedFileRecordHeader.size))
77 _LOG.debug('reading a new packed experiment file record')
78 header = PackedFileRecordHeader.unpack_from(b)
79 if header['version'] and not byte_order:
80 need_to_reorder = _need_to_reorder_bytes(header['version'])
81 byte_order = initial_byte_order = _byte_order(need_to_reorder)
83 'get byte order from version: {} (reorder? {})'.format(
84 byte_order, need_to_reorder))
86 PackedFileRecordHeader.byte_order = byte_order
87 PackedFileRecordHeader.setup()
88 header = PackedFileRecordHeader.unpack_from(b)
90 'reordered version: {}'.format(header['version']))
91 data = bytes(f.read(header['numDataBytes']))
92 if len(data) < header['numDataBytes']:
94 ('not enough data for the next record ({} < {})'
95 ).format(len(b), header['numDataBytes']))
96 record_type = _RECORD_TYPE.get(
97 header['recordType'] & PACKEDRECTYPE_MASK, _UnknownRecord)
98 _LOG.debug('the new record has type {} ({}).'.format(
99 record_type, header['recordType']))
100 if record_type in [_UnknownRecord, _UnusedRecord
101 ] and not ignore_unknown:
102 raise KeyError('unkown record type {}'.format(
103 header['recordType']))
104 records.append(record_type(header, data, byte_order=byte_order))
106 _LOG.debug('finished loading {} records from {}'.format(
107 len(records), filename))
108 if not hasattr(filename, 'read'):
111 filesystem = _build_filesystem(records)
113 return (records, filesystem)
115 def _build_filesystem(records):
117 """The name must be a valid Igor data folder name. See Object
118 Names in the Igor Reference help file for name rules.
120 When Igor Pro reads the data folder start record, it creates a new
121 data folder with the specified name. Any subsequent variable, wave
122 or data folder start records cause Igor to create data objects in
123 this new data folder, until Igor Pro reads a corresponding data
124 folder end record."""
125 # From the Igor Manual, chapter 2, section 8, page II-123
126 # http://www.wavemetrics.net/doc/igorman/II-08%20Data%20Folders.pdf
127 """Like the Macintosh file system, Igor Pro's data folders use the
128 colon character (:) to separate components of a path to an
129 object. This is analogous to Unix which uses / and Windows which
130 uses \. (Reminder: Igor's data folders exist wholly in memory
131 while an experiment is open. It is not a disk file system!)
133 A data folder named "root" always exists and contains all other
136 # From the Igor Manual, chapter 4, page IV-2
137 # http://www.wavemetrics.net/doc/igorman/IV-01%20Commands.pdf
138 """For waves and data folders only, you can also use "liberal"
139 names. Liberal names can include almost any character, including
140 spaces and dots (see Liberal Object Names on page III-415 for
143 # From the Igor Manual, chapter 3, section 16, page III-416
144 # http://www.wavemetrics.net/doc/igorman/III-16%20Miscellany.pdf
145 """Liberal names have the same rules as standard names except you
146 may use any character except control characters and the following:
150 filesystem = {'root': {}}
151 dir_stack = [('root', filesystem['root'])]
152 for record in records:
153 cwd = dir_stack[-1][-1]
154 if isinstance(record, _FolderStartRecord):
155 name = record.null_terminated_text
157 dir_stack.append((name, cwd[name]))
158 elif isinstance(record, _FolderEndRecord):
160 elif isinstance(record, (_VariablesRecord, _WaveRecord)):
161 if isinstance(record, _VariablesRecord):
162 sys_vars = record.variables['variables']['sysVars'].keys()
163 for filename,value in record.namespace.items():
164 if len(dir_stack) > 1 and filename in sys_vars:
166 """When reading a packed file, any system
167 variables encountered while the current data
168 folder is not the root should be ignored.
171 _check_filename(dir_stack, filename)
172 cwd[filename] = value
174 filename = record.wave['wave']['wave_header']['bname']
175 _check_filename(dir_stack, filename)
176 cwd[filename] = record
179 def _check_filename(dir_stack, filename):
180 cwd = dir_stack[-1][-1]
182 raise ValueError('collision on name {} in {}'.format(
183 filename, ':'.join(d for d,cwd in dir_stack)))