1 # This program is in the public domain
5 igor.load('filename') or igor.loads('data') loads the content of an igore file
6 into memory as a folder structure.
8 Returns the root folder.
10 Folders have name, path and children.
11 Children can be indexed by folder[i] or by folder['name'].
12 To see the whole tree, use: print folder.format()
14 The usual igor folder types are given in the technical reports
15 PTN003.ifn and TN003.ifn.
23 decode = lambda s: s.decode(sys.getfilesystemencoding())
54 class ParseObject(object):
55 """ Parent class for all objects the parser can return """
58 class Formula(ParseObject):
59 def __init__(self, formula, value):
60 self.formula = formula
63 class Variables(ParseObject):
65 Contains system numeric variables (e.g., K0) and user numeric and string variables.
67 def __init__(self, data, order):
68 version, = struct.unpack(order+"h",data[:2])
71 nSysVar, nUserVar, nUserStr \
72 = struct.unpack(order+"hhh",data[2:pos])
73 nDepVar, nDepStr = 0, 0
76 nSysVar, nUservar, nUserStr, nDepVar, nDepStr \
77 = struct.unpack(order+"hhh",data[2:pos])
79 raise ValueError("Unknown variable record version "+str(version))
80 self.sysvar, pos = _parse_sys_numeric(nSysVar, order, data, pos)
81 self.uservar, pos = _parse_user_numeric(nUserVar, order, data, pos)
83 self.userstr, pos = _parse_user_string1(nUserStr, order, data, pos)
85 self.userstr, pos = _parse_user_string2(nUserStr, order, data, pos)
86 self.depvar, pos = _parse_dep_numeric(nDepVar, order, data, pos)
87 self.depstr, pos = _parse_dep_string(nDepStr, order, data, pos)
88 def format(self, indent=0):
89 return " "*indent+"<Variables: system %d, user %d, dependent %s>"\
91 len(self.uservar)+len(self.userstr),
92 len(self.depvar)+len(self.depstr))
94 class History(ParseObject):
96 Contains the experiment's history as plain text.
98 def __init__(self, data, order): self.data = data
99 def format(self, indent=0):
100 return " "*indent+"<History>"
102 class Wave(ParseObject):
104 Contains the data for a wave
106 def __init__(self, data, order):
107 version, = struct.unpack(order+'h',data[:2])
110 extra_offset,checksum = struct.unpack(order+'ih',data[2:pos])
111 formula_size = note_size = pic_size = 0
114 extra_offset,note_size,pic_size,checksum \
115 = struct.unpack(order+'iiih',data[2:pos])
119 extra_offset,note_size,formula_size,pic_size,checksum \
120 = struct.unpack(order+'iiiih',data[2:pos])
122 checksum,extra_offset,formula_size,note_size, \
123 = struct.unpack(order+'hiii',data[2:16])
124 Esize = struct.unpack(order+'iiiiiiiii',data[16:52])
125 textindsize, = struct.unpack('i',data[52:56])
129 raise ValueError("unknown wave version "+str(version))
132 if version in (1,2,3):
133 type, = struct.unpack(order+'h',data[pos:pos+2])
134 name = data[pos+6:data.find(chr(0),pos+6,pos+26)]
135 #print "name3",name,type
136 data_units = data[pos+34:data.find(chr(0),pos+34,pos+38)]
137 xaxis = data[pos+38:data.find(chr(0),pos+38,pos+42)]
138 points, = struct.unpack(order+'i',data[pos+42:pos+46])
139 hsA,hsB = struct.unpack(order+'dd',data[pos+48:pos+64])
140 fsValid,fsTop,fsBottom \
141 = struct.unpack(order+'hdd',data[pos+70:pos+88])
142 created,_,modified = struct.unpack(order+'IhI',data[pos+98:pos+108])
144 dims = (points,0,0,0)
145 sf = (hsA,0,0,0,hsB,0,0,0)
146 axis_units = (xaxis,"","","")
148 created,modified,points,type \
149 = struct.unpack(order+'IIih',data[pos+4:pos+18])
150 name = data[pos+28:data.find(chr(0),pos+28,pos+60)]
151 #print "name5",name,type
152 dims = struct.unpack(order+'iiii',data[pos+68:pos+84])
153 sf = struct.unpack(order+'dddddddd',data[pos+84:pos+148])
154 data_units = data[pos+148:data.find(chr(0),pos+148,pos+152)]
155 axis_units = tuple(data[pos+152+4*i
156 : data.find(chr(0),pos+152+4*i,pos+156+4*i)]
158 fsValid,_,fsTop,fsBottom \
159 = struct.unpack(order+'hhdd',data[pos+172:pos+192])
163 text = data[pos:extra_offset]
164 textind = numpy.fromstring(data[-textindsize:], order+'i')
165 textind = numpy.hstack((0,textind))
166 value = [text[textind[i]:textind[i+1]]
167 for i in range(len(textind)-1)]
169 trimdims = tuple(d for d in dims if d)
170 dtype = order+ORDER_NUMTYPE[type]
171 size = int(dtype[2:])*numpy.prod(trimdims)
172 value = numpy.fromstring(data[pos:pos+size],dtype)
173 value = value.reshape(trimdims)
176 formula = data[pos:pos+formula_size]
178 notes = data[pos:pos+note_size]
181 offset = numpy.cumsum(numpy.hstack((pos,Esize)))
182 Edata_units = data[offset[0]:offset[1]]
183 Eaxis_units = [data[offset[i]:offset[i+1]] for i in range(1,5)]
184 Eaxis_labels = [data[offset[i]:offset[i+1]] for i in range(5,9)]
185 if Edata_units: data_units = Edata_units
186 for i,u in enumerate(Eaxis_units):
187 if u: axis_units[i] = u
188 axis_labels = Eaxis_labels
192 self.name = decode(name)
194 self.data_units = data_units
195 self.axis_units = axis_units
196 self.fs,self.fstop,self.fsbottom = fsValid,fsTop,fsBottom
197 self.axis = [numpy.linspace(a,b,n)
198 for a,b,n in zip(sf[:4],sf[4:],dims)]
199 self.formula = formula
201 def format(self, indent=0):
202 if isinstance(self.data, list):
203 type,size = "text", "%d"%len(self.data)
205 type,size = "data", "x".join(str(d) for d in self.data.shape)
206 return " "*indent+"%s %s (%s)"%(self.name, type, size)
211 __repr__ = __str__ = lambda s: u"<igor.Wave %s>" % s.format()
213 class Recreation(ParseObject):
215 Contains the experiment's recreation procedures as plain text.
217 def __init__(self, data, order): self.data = data
218 def format(self, indent=0):
219 return " "*indent + "<Recreation>"
220 class Procedure(ParseObject):
222 Contains the experiment's main procedure window text as plain text.
224 def __init__(self, data, order): self.data = data
225 def format(self, indent=0):
226 return " "*indent + "<Procedure>"
227 class GetHistory(ParseObject):
229 Not a real record but rather, a message to go back and read the history text.
231 The reason for GetHistory is that IGOR runs Recreation when it loads the
232 datafile. This puts entries in the history that shouldn't be there. The
233 GetHistory entry simply says that the Recreation has run, and the History
234 can be restored from the previously saved value.
236 def __init__(self, data, order): self.data = data
237 def format(self, indent=0):
238 return " "*indent + "<GetHistory>"
239 class PackedFile(ParseObject):
241 Contains the data for a procedure file or notebook in packed form.
243 def __init__(self, data, order): self.data = data
244 def format(self, indent=0):
245 return " "*indent + "<PackedFile>"
246 class Unknown(ParseObject):
248 Record type not documented in PTN003/TN003.
250 def __init__(self, data, order, type):
253 def format(self, indent=0):
254 return " "*indent + "<Unknown type %s>"%self.type
256 class _FolderStart(ParseObject):
258 Marks the start of a new data folder.
260 def __init__(self, data, order):
261 self.name = decode(data[:data.find(chr(0))])
262 class _FolderEnd(ParseObject):
264 Marks the end of a data folder.
266 def __init__(self, data, order): self.data = data
268 class Folder(object):
270 Hierarchical record container.
272 def __init__(self, path):
277 def __getitem__(self, key):
278 if isinstance(key, int):
279 return self.children[key]
281 for r in self.children:
282 if isinstance(r, (Folder,Wave)) and r.name == key:
284 raise KeyError("Folder %s does not exist"%key)
287 return u"<igor.Folder %s>" % "/".join(self.path)
291 def append(self, record):
292 self.children.append(record)
294 setattr(self, record.name, record)
295 except AttributeError:
298 def format(self, indent=0):
299 parent = u" "*indent+self.name
300 children = [r.format(indent=indent+2) for r in self.children]
301 return u"\n".join([parent]+children)
315 def loads(s, ignore_unknown=True):
316 """Load an igor file from string"""
320 stack = [Folder(path=[u'root'])]
323 raise IOError("invalid record header; bad pxp file?")
324 ignore = ord(s[pos])&0x80
325 order = '<' if ord(s[pos])&0x77 else '>'
326 type, version, length = struct.unpack(order+'hhi',s[pos:pos+8])
328 if pos+length > len(s):
329 raise IOError("final record too long; bad pxp file?")
330 data = s[pos:pos+length]
333 parse = PARSER.get(type, None)
335 record = parse(data, order)
339 record = Unknown(data=data, order=order, type=type)
340 if isinstance(record, _FolderStart):
341 path = stack[-1].path+[record.name]
342 folder = Folder(path)
343 stack[-1].append(folder)
345 elif isinstance(record, _FolderEnd):
348 stack[-1].append(record)
350 raise IOError("FolderStart records do not match FolderEnd records")
353 def load(filename, ignore_unknown=True):
354 """Load an igor file"""
355 return loads(open(filename,'rb').read(),
356 ignore_unknown=ignore_unknown)
358 # ============== Variable parsing ==============
359 def _parse_sys_numeric(n, order, data, pos):
360 values = numpy.fromstring(data[pos:pos+n*4], order+'f')
362 var = dict(('K'+str(i),v) for i,v in enumerate(values))
365 def _parse_user_numeric(n, order, data, pos):
368 name = data[pos:data.find(chr(0),pos,pos+32)]
369 type,numtype,real,imag = struct.unpack(order+"hhdd",data[pos+32:pos+52])
370 dtype = NUMTYPE[numtype]
371 if dtype in (numpy.complex64, numpy.complex128):
372 value = dtype(real+1j*imag)
379 def _parse_dep_numeric(n, order, data, pos):
382 name = data[pos:data.find(chr(0),pos,pos+32)]
383 type,numtype,real,imag = struct.unpack(order+"hhdd",data[pos+32:pos+52])
384 dtype = NUMTYPE[numtype]
385 if dtype in (numpy.complex64, numpy.complex128):
386 value = dtype(real+1j*imag)
389 length, = struct.unpack(order+"h",data[pos+56:pos+58])
390 var[name] = Formula(data[pos+58:pos+58+length-1], value)
394 def _parse_dep_string(n, order, data, pos):
397 name = data[pos:data.find(chr(0),pos,pos+32)]
398 length, = struct.unpack(order+"h",data[pos+48:pos+50])
399 var[name] = Formula(data[pos+50:pos+50+length-1], "")
403 def _parse_user_string1(n, order, data, pos):
406 name = data[pos:data.find(chr(0),pos,pos+32)]
407 length, = struct.unpack(order+"h",data[pos+32:pos+34])
408 value = data[pos+34:pos+34+length]
413 def _parse_user_string2(n, order, data, pos):
416 name = data[pos:data.find(chr(0),pos,pos+32)]
417 length, = struct.unpack(order+"i",data[pos+32:pos+36])
418 value = data[pos+36:pos+36+length]