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):
276 def __getitem__(self, key):
277 if isinstance(key, int):
278 return self.children[key]
280 for r in self.children:
281 if isinstance(r, (Folder,Wave)) and r.name == key:
283 raise KeyError("Folder %s does not exist"%key)
286 return u"<igor.Folder %s>" % "/".join(self.path)
290 def append(self, record):
291 self.children.append(record)
293 setattr(self, record.name, record)
294 except AttributeError:
297 def format(self, indent=0):
298 parent = u" "*indent+self.name
299 children = [r.format(indent=indent+2) for r in self.children]
300 return u"\n".join([parent]+children)
314 def loads(s, ignore_unknown=True):
315 """Load an igor file from string"""
319 stack = [Folder(path=[u'root'])]
322 raise IOError("invalid record header; bad pxp file?")
323 ignore = ord(s[pos])&0x80
324 order = '<' if ord(s[pos])&0x77 else '>'
325 type, version, length = struct.unpack(order+'hhi',s[pos:pos+8])
327 if pos+length > len(s):
328 raise IOError("final record too long; bad pxp file?")
329 data = s[pos:pos+length]
332 parse = PARSER.get(type, None)
334 record = parse(data, order)
338 record = Unknown(data=data, order=order, type=type)
339 if isinstance(record, _FolderStart):
340 path = stack[-1].path+[record.name]
341 folder = Folder(path)
342 stack[-1].append(folder)
344 elif isinstance(record, _FolderEnd):
347 stack[-1].append(record)
349 raise IOError("FolderStart records do not match FolderEnd records")
352 def load(filename, ignore_unknown=True):
353 """Load an igor file"""
354 return loads(open(filename,'rb').read(),
355 ignore_unknown=ignore_unknown)
357 # ============== Variable parsing ==============
358 def _parse_sys_numeric(n, order, data, pos):
359 values = numpy.fromstring(data[pos:pos+n*4], order+'f')
361 var = dict(('K'+str(i),v) for i,v in enumerate(values))
364 def _parse_user_numeric(n, order, data, pos):
367 name = data[pos:data.find(chr(0),pos,pos+32)]
368 type,numtype,real,imag = struct.unpack(order+"hhdd",data[pos+32:pos+52])
369 dtype = NUMTYPE[numtype]
370 if dtype in (numpy.complex64, numpy.complex128):
371 value = dtype(real+1j*imag)
378 def _parse_dep_numeric(n, order, data, pos):
381 name = data[pos:data.find(chr(0),pos,pos+32)]
382 type,numtype,real,imag = struct.unpack(order+"hhdd",data[pos+32:pos+52])
383 dtype = NUMTYPE[numtype]
384 if dtype in (numpy.complex64, numpy.complex128):
385 value = dtype(real+1j*imag)
388 length, = struct.unpack(order+"h",data[pos+56:pos+58])
389 var[name] = Formula(data[pos+58:pos+58+length-1], value)
393 def _parse_dep_string(n, order, data, pos):
396 name = data[pos:data.find(chr(0),pos,pos+32)]
397 length, = struct.unpack(order+"h",data[pos+48:pos+50])
398 var[name] = Formula(data[pos+50:pos+50+length-1], "")
402 def _parse_user_string1(n, order, data, pos):
405 name = data[pos:data.find(chr(0),pos,pos+32)]
406 length, = struct.unpack(order+"h",data[pos+32:pos+34])
407 value = data[pos+34:pos+34+length]
412 def _parse_user_string2(n, order, data, pos):
415 name = data[pos:data.find(chr(0),pos,pos+32)]
416 length, = struct.unpack(order+"i",data[pos+32:pos+36])
417 value = data[pos+36:pos+36+length]