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.
24 decode = lambda s: s.decode(sys.getfilesystemencoding())
26 PYKEYWORDS = set(('and','as','assert','break','class','continue',
27 'def','elif','else','except','exec','finally',
28 'for','global','if','import','in','is','lambda',
29 'or','pass','print','raise','return','try','with',
31 PYID = re.compile(r"^[^\d\W]\w*$", re.UNICODE)
32 def valid_identifier(s):
33 """Check if a name is a valid identifier"""
34 return PYID.match(s) and s not in PYKEYWORDS
65 class IgorObject(object):
66 """ Parent class for all objects the parser can return """
69 class Formula(IgorObject):
70 def __init__(self, formula, value):
71 self.formula = formula
74 class Variables(IgorObject):
76 Contains system numeric variables (e.g., K0) and user numeric and string variables.
78 def __init__(self, data, order):
79 version, = struct.unpack(order+"h",data[:2])
82 nSysVar, nUserVar, nUserStr \
83 = struct.unpack(order+"hhh",data[2:pos])
84 nDepVar, nDepStr = 0, 0
87 nSysVar, nUservar, nUserStr, nDepVar, nDepStr \
88 = struct.unpack(order+"hhh",data[2:pos])
90 raise ValueError("Unknown variable record version "+str(version))
91 self.sysvar, pos = _parse_sys_numeric(nSysVar, order, data, pos)
92 self.uservar, pos = _parse_user_numeric(nUserVar, order, data, pos)
94 self.userstr, pos = _parse_user_string1(nUserStr, order, data, pos)
96 self.userstr, pos = _parse_user_string2(nUserStr, order, data, pos)
97 self.depvar, pos = _parse_dep_numeric(nDepVar, order, data, pos)
98 self.depstr, pos = _parse_dep_string(nDepStr, order, data, pos)
99 def format(self, indent=0):
100 return " "*indent+"<Variables: system %d, user %d, dependent %s>"\
102 len(self.uservar)+len(self.userstr),
103 len(self.depvar)+len(self.depstr))
105 class History(IgorObject):
107 Contains the experiment's history as plain text.
109 def __init__(self, data, order): self.data = data
110 def format(self, indent=0):
111 return " "*indent+"<History>"
113 class Wave(IgorObject):
115 Contains the data for a wave
117 def __init__(self, data, order):
118 version, = struct.unpack(order+'h',data[:2])
121 extra_offset,checksum = struct.unpack(order+'ih',data[2:pos])
122 formula_size = note_size = pic_size = 0
125 extra_offset,note_size,pic_size,checksum \
126 = struct.unpack(order+'iiih',data[2:pos])
130 extra_offset,note_size,formula_size,pic_size,checksum \
131 = struct.unpack(order+'iiiih',data[2:pos])
133 checksum,extra_offset,formula_size,note_size, \
134 = struct.unpack(order+'hiii',data[2:16])
135 Esize = struct.unpack(order+'iiiiiiiii',data[16:52])
136 textindsize, = struct.unpack('i',data[52:56])
140 raise ValueError("unknown wave version "+str(version))
143 if version in (1,2,3):
144 type, = struct.unpack(order+'h',data[pos:pos+2])
145 name = data[pos+6:data.find(chr(0),pos+6,pos+26)]
146 #print "name3",name,type
147 data_units = data[pos+34:data.find(chr(0),pos+34,pos+38)]
148 xaxis = data[pos+38:data.find(chr(0),pos+38,pos+42)]
149 points, = struct.unpack(order+'i',data[pos+42:pos+46])
150 hsA,hsB = struct.unpack(order+'dd',data[pos+48:pos+64])
151 fsValid,fsTop,fsBottom \
152 = struct.unpack(order+'hdd',data[pos+70:pos+88])
153 created,_,modified = struct.unpack(order+'IhI',data[pos+98:pos+108])
155 dims = (points,0,0,0)
156 sf = (hsA,0,0,0,hsB,0,0,0)
157 axis_units = (xaxis,"","","")
159 created,modified,points,type \
160 = struct.unpack(order+'IIih',data[pos+4:pos+18])
161 name = data[pos+28:data.find(chr(0),pos+28,pos+60)]
162 #print "name5",name,type
163 dims = struct.unpack(order+'iiii',data[pos+68:pos+84])
164 sf = struct.unpack(order+'dddddddd',data[pos+84:pos+148])
165 data_units = data[pos+148:data.find(chr(0),pos+148,pos+152)]
166 axis_units = tuple(data[pos+152+4*i
167 : data.find(chr(0),pos+152+4*i,pos+156+4*i)]
169 fsValid,_,fsTop,fsBottom \
170 = struct.unpack(order+'hhdd',data[pos+172:pos+192])
174 text = data[pos:extra_offset]
175 textind = numpy.fromstring(data[-textindsize:], order+'i')
176 textind = numpy.hstack((0,textind))
177 value = [text[textind[i]:textind[i+1]]
178 for i in range(len(textind)-1)]
180 trimdims = tuple(d for d in dims if d)
181 dtype = order+ORDER_NUMTYPE[type]
182 size = int(dtype[2:])*numpy.prod(trimdims)
183 value = numpy.fromstring(data[pos:pos+size],dtype)
184 value = value.reshape(trimdims)
187 formula = data[pos:pos+formula_size]
189 notes = data[pos:pos+note_size]
192 offset = numpy.cumsum(numpy.hstack((pos,Esize)))
193 Edata_units = data[offset[0]:offset[1]]
194 Eaxis_units = [data[offset[i]:offset[i+1]] for i in range(1,5)]
195 Eaxis_labels = [data[offset[i]:offset[i+1]] for i in range(5,9)]
196 if Edata_units: data_units = Edata_units
197 for i,u in enumerate(Eaxis_units):
198 if u: axis_units[i] = u
199 axis_labels = Eaxis_labels
203 self.name = decode(name)
205 self.data_units = data_units
206 self.axis_units = axis_units
207 self.fs,self.fstop,self.fsbottom = fsValid,fsTop,fsBottom
208 self.axis = [numpy.linspace(a,b,n)
209 for a,b,n in zip(sf[:4],sf[4:],dims)]
210 self.formula = formula
212 def format(self, indent=0):
213 if isinstance(self.data, list):
214 type,size = "text", "%d"%len(self.data)
216 type,size = "data", "x".join(str(d) for d in self.data.shape)
217 return " "*indent+"%s %s (%s)"%(self.name, type, size)
222 __repr__ = __str__ = lambda s: u"<igor.Wave %s>" % s.format()
224 class Recreation(IgorObject):
226 Contains the experiment's recreation procedures as plain text.
228 def __init__(self, data, order): self.data = data
229 def format(self, indent=0):
230 return " "*indent + "<Recreation>"
231 class Procedure(IgorObject):
233 Contains the experiment's main procedure window text as plain text.
235 def __init__(self, data, order): self.data = data
236 def format(self, indent=0):
237 return " "*indent + "<Procedure>"
238 class GetHistory(IgorObject):
240 Not a real record but rather, a message to go back and read the history text.
242 The reason for GetHistory is that IGOR runs Recreation when it loads the
243 datafile. This puts entries in the history that shouldn't be there. The
244 GetHistory entry simply says that the Recreation has run, and the History
245 can be restored from the previously saved value.
247 def __init__(self, data, order): self.data = data
248 def format(self, indent=0):
249 return " "*indent + "<GetHistory>"
250 class PackedFile(IgorObject):
252 Contains the data for a procedure file or notebook in packed form.
254 def __init__(self, data, order): self.data = data
255 def format(self, indent=0):
256 return " "*indent + "<PackedFile>"
257 class Unknown(IgorObject):
259 Record type not documented in PTN003/TN003.
261 def __init__(self, data, order, type):
264 def format(self, indent=0):
265 return " "*indent + "<Unknown type %s>"%self.type
267 class _FolderStart(IgorObject):
269 Marks the start of a new data folder.
271 def __init__(self, data, order):
272 self.name = decode(data[:data.find(chr(0))])
273 class _FolderEnd(IgorObject):
275 Marks the end of a data folder.
277 def __init__(self, data, order): self.data = data
279 class Folder(IgorObject):
281 Hierarchical record container.
283 def __init__(self, path):
288 def __getitem__(self, key):
289 if isinstance(key, int):
290 return self.children[key]
292 for r in self.children:
293 if isinstance(r, (Folder,Wave)) and r.name == key:
295 raise KeyError("Folder %s does not exist"%key)
298 return u"<igor.Folder %s>" % "/".join(self.path)
302 def append(self, record):
304 Add a record to the folder.
306 self.children.append(record)
308 # Record may not have a name, the name may be invalid, or it
309 # may already be in use. The noname case will be covered by
310 # record.name raising an attribute error. The others we need
311 # to test for explicitly.
312 if valid_identifier(record.name) and not hasattr(self, record.name):
313 setattr(self, record.name, record)
314 except AttributeError:
317 def format(self, indent=0):
318 parent = u" "*indent+self.name
319 children = [r.format(indent=indent+2) for r in self.children]
320 return u"\n".join([parent]+children)
334 def loads(s, ignore_unknown=True):
335 """Load an igor file from string"""
339 stack = [Folder(path=[u'root'])]
342 raise IOError("invalid record header; bad pxp file?")
343 ignore = ord(s[pos])&0x80
344 order = '<' if ord(s[pos])&0x77 else '>'
345 type, version, length = struct.unpack(order+'hhi',s[pos:pos+8])
347 if pos+length > len(s):
348 raise IOError("final record too long; bad pxp file?")
349 data = s[pos:pos+length]
352 parse = PARSER.get(type, None)
354 record = parse(data, order)
358 record = Unknown(data=data, order=order, type=type)
359 if isinstance(record, _FolderStart):
360 path = stack[-1].path+[record.name]
361 folder = Folder(path)
362 stack[-1].append(folder)
364 elif isinstance(record, _FolderEnd):
367 stack[-1].append(record)
369 raise IOError("FolderStart records do not match FolderEnd records")
372 def load(filename, ignore_unknown=True):
373 """Load an igor file"""
374 return loads(open(filename,'rb').read(),
375 ignore_unknown=ignore_unknown)
377 # ============== Variable parsing ==============
378 def _parse_sys_numeric(n, order, data, pos):
379 values = numpy.fromstring(data[pos:pos+n*4], order+'f')
381 var = dict(('K'+str(i),v) for i,v in enumerate(values))
384 def _parse_user_numeric(n, order, data, pos):
387 name = data[pos:data.find(chr(0),pos,pos+32)]
388 type,numtype,real,imag = struct.unpack(order+"hhdd",data[pos+32:pos+52])
389 dtype = NUMTYPE[numtype]
390 if dtype in (numpy.complex64, numpy.complex128):
391 value = dtype(real+1j*imag)
398 def _parse_dep_numeric(n, order, data, pos):
401 name = data[pos:data.find(chr(0),pos,pos+32)]
402 type,numtype,real,imag = struct.unpack(order+"hhdd",data[pos+32:pos+52])
403 dtype = NUMTYPE[numtype]
404 if dtype in (numpy.complex64, numpy.complex128):
405 value = dtype(real+1j*imag)
408 length, = struct.unpack(order+"h",data[pos+56:pos+58])
409 var[name] = Formula(data[pos+58:pos+58+length-1], value)
413 def _parse_dep_string(n, order, data, pos):
416 name = data[pos:data.find(chr(0),pos,pos+32)]
417 length, = struct.unpack(order+"h",data[pos+48:pos+50])
418 var[name] = Formula(data[pos+50:pos+50+length-1], "")
422 def _parse_user_string1(n, order, data, pos):
425 name = data[pos:data.find(chr(0),pos,pos+32)]
426 length, = struct.unpack(order+"h",data[pos+32:pos+34])
427 value = data[pos+34:pos+34+length]
432 def _parse_user_string2(n, order, data, pos):
435 name = data[pos:data.find(chr(0),pos,pos+32)]
436 length, = struct.unpack(order+"i",data[pos+32:pos+36])
437 value = data[pos+36:pos+36+length]