(fit.py) Quick kludgy patch for crash of issue 0028
[hooke.git] / libhooke.py
1 #!/usr/bin/env python
2
3 '''
4 libhooke.py
5
6 General library of internal objects and utilities for Hooke.
7
8 Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy).
9 With algorithms contributed by Francesco Musiani (University of Bologna, Italy)
10
11 This program is released under the GNU General Public License version 2.
12 '''
13
14
15
16 import libhookecurve as lhc
17
18 import scipy
19 import scipy.signal
20 import scipy.optimize
21 import scipy.stats
22 import numpy
23 import xml.dom.minidom
24 import os
25 import string
26 import csv
27
28 HOOKE_VERSION=['0.8.3_devel', 'Seinei', '2008-04-16']
29 WX_GOOD=['2.6','2.8'] 
30     
31 class PlaylistXML:
32         '''
33         This module allows for import/export of an XML playlist into/out of a list of HookeCurve objects
34         '''
35         
36         def __init__(self):
37             
38             self.playlist=None #the DOM object representing the playlist data structure
39             self.playpath=None #the path of the playlist XML file
40             self.plaything=None
41             self.hidden_attributes=['curve'] #This list contains hidden attributes that we don't want to go into the playlist.
42         
43         def export(self, list_of_hooke_curves, generics):
44             '''
45             Creates an initial playlist from a list of files.
46             A playlist is an XML document with the following syntaxis:
47             <playlist>
48             <element path="/my/file/path/"/ attribute="attribute">
49             <element path="...">
50             </playlist>
51             '''   
52         
53             #create the output playlist, a simple XML document
54             impl=xml.dom.minidom.getDOMImplementation()
55             #create the document DOM object and the root element
56             newdoc=impl.createDocument(None, "playlist",None)
57             top_element=newdoc.documentElement
58             
59             #save generics variables
60             playlist_generics=newdoc.createElement("generics")
61             top_element.appendChild(playlist_generics)
62             for key in generics.keys():
63                 newdoc.createAttribute(key)
64                 playlist_generics.setAttribute(key,str(generics[key]))
65             
66             #save curves and their attributes
67             for item in list_of_hooke_curves:
68                 #playlist_element=newdoc.createElement("curve")
69                 playlist_element=newdoc.createElement("element")
70                 top_element.appendChild(playlist_element)
71                 for key in item.__dict__:
72                     if not (key in self.hidden_attributes):
73                         newdoc.createAttribute(key)
74                         playlist_element.setAttribute(key,str(item.__dict__[key]))    
75             
76             self.playlist=newdoc
77             
78         def load(self,filename):
79             '''
80             loads a playlist file
81             '''
82             myplay=file(filename)
83             self.playpath=filename
84             
85             #the following 3 lines are needed to strip newlines. otherwise, since newlines
86             #are XML elements too (why?), the parser would read them (and re-save them, multiplying
87             #newlines...)
88             #yes, I'm an XML n00b
89             the_file=myplay.read()
90             the_file_lines=the_file.split('\n')
91             the_file=''.join(the_file_lines)
92                        
93             self.playlist=xml.dom.minidom.parseString(the_file)  
94                            
95             #inner parsing functions
96             def handlePlaylist(playlist):
97                 list_of_files=playlist.getElementsByTagName("element")
98                 generics=playlist.getElementsByTagName("generics")
99                 return handleFiles(list_of_files), handleGenerics(generics)
100             
101             def handleGenerics(generics):
102                 generics_dict={}
103                 if len(generics)==0:
104                     return generics_dict
105                 
106                 for attribute in generics[0].attributes.keys():
107                     generics_dict[attribute]=generics[0].getAttribute(attribute)
108                 return generics_dict
109         
110             def handleFiles(list_of_files):
111                 new_playlist=[]
112                 for myfile in list_of_files:
113                     #rebuild a data structure from the xml attributes
114                     the_curve=lhc.HookeCurve(myfile.getAttribute('path'))
115                     for attribute in myfile.attributes.keys(): #extract attributes for the single curve
116                         the_curve.__dict__[attribute]=myfile.getAttribute(attribute)
117                     new_playlist.append(the_curve)
118                 
119                 return new_playlist #this is the true thing returned at the end of this function...(FIXME: clarity)
120                     
121             return handlePlaylist(self.playlist)
122             
123
124         def save(self,output_filename):
125             '''
126             saves the playlist in a XML file.
127             '''
128             try:
129                 outfile=file(output_filename,'w')
130             except IOError:
131                 print 'libhooke.py : Cannot save playlist. Wrong path or filename'
132                 return
133             
134             self.playlist.writexml(outfile,indent='\n')
135             outfile.close()
136
137
138 class HookeConfig:
139     '''
140     Handling of Hooke configuration file
141     
142     Mostly based on the simple-yet-useful examples of the Python Library Reference
143     about xml.dom.minidom
144     
145     FIXME: starting to look a mess, should require refactoring
146     '''
147     
148     def __init__(self):
149         self.config={}
150         self.config['plugins']=[]
151         self.config['drivers']=[]
152         self.config['plotmanips']=[]
153                 
154     def load_config(self, filename):
155         myconfig=file(filename)
156                     
157         #the following 3 lines are needed to strip newlines. otherwise, since newlines
158         #are XML elements too, the parser would read them (and re-save them, multiplying
159         #newlines...)
160         #yes, I'm an XML n00b
161         the_file=myconfig.read()
162         the_file_lines=the_file.split('\n')
163         the_file=''.join(the_file_lines)
164                        
165         self.config_tree=xml.dom.minidom.parseString(the_file)  
166         
167         def getText(nodelist):
168             #take the text from a nodelist
169             #from Python Library Reference 13.7.2
170             rc = ''
171             for node in nodelist:
172                 if node.nodeType == node.TEXT_NODE:
173                     rc += node.data
174             return rc
175         
176         def handleConfig(config):
177             display_elements=config.getElementsByTagName("display")
178             plugins_elements=config.getElementsByTagName("plugins")
179             drivers_elements=config.getElementsByTagName("drivers")
180             workdir_elements=config.getElementsByTagName("workdir")
181             defaultlist_elements=config.getElementsByTagName("defaultlist")
182             plotmanip_elements=config.getElementsByTagName("plotmanips")
183             handleDisplay(display_elements)
184             handlePlugins(plugins_elements)
185             handleDrivers(drivers_elements)
186             handleWorkdir(workdir_elements)
187             handleDefaultlist(defaultlist_elements)
188             handlePlotmanip(plotmanip_elements)
189             
190         def handleDisplay(display_elements):
191             for element in display_elements:
192                 for attribute in element.attributes.keys():
193                     self.config[attribute]=element.getAttribute(attribute)
194                     
195         def handlePlugins(plugins):
196             for plugin in plugins[0].childNodes:
197                 try:
198                     self.config['plugins'].append(str(plugin.tagName))
199                 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
200                     pass
201         #FIXME: code duplication
202         def handleDrivers(drivers):
203             for driver in drivers[0].childNodes:
204                 try:
205                     self.config['drivers'].append(str(driver.tagName))
206                 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
207                     pass
208         
209         def handlePlotmanip(plotmanips):
210             for plotmanip in plotmanips[0].childNodes:
211                 try:
212                     self.config['plotmanips'].append(str(plotmanip.tagName))
213                 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
214                     pass
215         
216         def handleWorkdir(workdir):
217             '''
218             default working directory
219             '''
220             wdir=getText(workdir[0].childNodes)
221             self.config['workdir']=wdir.strip()
222             
223         def handleDefaultlist(defaultlist):
224             '''
225             default playlist
226             '''
227             dflist=getText(defaultlist[0].childNodes)
228             self.config['defaultlist']=dflist.strip()
229             
230         handleConfig(self.config_tree)
231         #making items in the dictionary more machine-readable
232         for item in self.config.keys():
233             try:
234                 self.config[item]=float(self.config[item])
235             except TypeError: #we are dealing with a list, probably. keep it this way.
236                 try:
237                     self.config[item]=eval(self.config[item])
238                 except: #not a list, not a tuple, probably a string?
239                     pass
240             except ValueError: #if we can't get it to a number, it must be None or a string
241                 if string.lower(self.config[item])=='none':
242                     self.config[item]=None
243                 else:
244                     pass
245                                                 
246         return self.config
247         
248         
249     def save_config(self, config_filename):
250         print 'Not Implemented.'
251         pass    
252
253
254 class ClickedPoint:
255     '''
256     this class defines what a clicked point on the curve plot is
257     '''
258     def __init__(self):
259         
260         self.is_marker=None #boolean ; decides if it is a marker
261         self.is_line_edge=None #boolean ; decides if it is the edge of a line (unused)
262         self.absolute_coords=(None,None) #(float,float) ; the absolute coordinates of the clicked point on the graph
263         self.graph_coords=(None,None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point
264         self.index=None #integer ; the index of the clicked point with respect to the vector selected
265         self.dest=None #0 or 1 ; 0=top plot 1=bottom plot
266                 
267         
268     def find_graph_coords_old(self, xvector, yvector):
269         '''
270         Given a clicked point on the plot, finds the nearest point in the dataset (in X) that
271         corresponds to the clicked point.
272         OLD & DEPRECATED - to be removed
273         '''
274                    
275         #FIXME: a general algorithm using min() is needed!
276         print '---DEPRECATED FIND_GRAPH_COORDS_OLD---'
277         best_index=0
278         best_dist=10**9 #should be more than enough given the scale
279                 
280         for index in scipy.arange(1,len(xvector),1):
281             dist=((self.absolute_coords[0]-xvector[index])**2)+(100*((self.absolute_coords[1]-yvector[index])))**2
282                         #TODO, generalize? y coordinate is multiplied by 100 due to scale differences in the plot
283             if dist<best_dist:
284                 best_index=index
285                 best_dist=dist
286                         
287         self.index=best_index
288         self.graph_coords=(xvector[best_index],yvector[best_index])
289         return
290             
291     def find_graph_coords(self,xvector,yvector):
292         '''
293         Given a clicked point on the plot, finds the nearest point in the dataset that
294         corresponds to the clicked point.
295         '''
296         dists=[]
297         for index in scipy.arange(1,len(xvector),1):
298             dists.append(((self.absolute_coords[0]-xvector[index])**2)+((self.absolute_coords[1]-yvector[index])**2))
299                         
300         self.index=dists.index(min(dists))
301         self.graph_coords=(xvector[self.index],yvector[self.index])
302 #-----------------------------------------
303 #CSV-HELPING FUNCTIONS        
304         
305 def transposed2(lists, defval=0):
306     '''
307     transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
308     elements
309     (by Zoran Isailovski on the Python Cookbook online)
310     '''
311     if not lists: return []
312     return map(lambda *row: [elem or defval for elem in row], *lists)
313         
314 def csv_write_dictionary(f, data, sorting='COLUMNS'):
315     '''
316     Writes a CSV file from a dictionary, with keys as first column or row
317     Keys are in "random" order.
318     
319     Keys should be strings
320     Values should be lists or other iterables
321     '''
322     keys=data.keys()
323     values=data.values()
324     t_values=transposed2(values)
325     writer=csv.writer(f)
326
327     if sorting=='COLUMNS':
328         writer.writerow(keys)
329         for item in t_values:
330             writer.writerow(item)
331         
332     if sorting=='ROWS':
333         print 'Not implemented!' #FIXME: implement it.
334
335
336 #-----------------------------------------        
337                     
338 def debug():
339     '''
340     debug stuff from latest rewrite of hooke_playlist.py
341     should be removed sooner or later (or substituted with new debug code!)
342     '''
343     confo=HookeConfig()
344     print confo.load_config('hooke.conf')
345
346 if __name__ == '__main__':
347     debug()