(pcluster.py) testing stuff for test datasets
[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         '''
273                    
274         #FIXME: a general algorithm using min() is needed!
275         best_index=0
276         best_dist=10**9 #should be more than enough given the scale
277                 
278         for index in scipy.arange(1,len(xvector),1):
279             dist=((self.absolute_coords[0]-xvector[index])**2)+(100*((self.absolute_coords[1]-yvector[index])))**2
280                         #TODO, generalize? y coordinate is multiplied by 100 due to scale differences in the plot
281             if dist<best_dist:
282                 best_index=index
283                 best_dist=dist
284                         
285         self.index=best_index
286         self.graph_coords=(xvector[best_index],yvector[best_index])
287         return
288             
289     def find_graph_coords(self,xvector,yvector):
290         '''
291         Given a clicked point on the plot, finds the nearest point in the dataset (in X) that
292         corresponds to the clicked point.
293         '''
294         dists=[]
295         for index in scipy.arange(1,len(xvector),1):
296             dists.append(((self.absolute_coords[0]-xvector[index])**2)+((self.absolute_coords[1]-yvector[index])**2))
297                         
298         self.index=dists.index(min(dists))
299         self.graph_coords=(xvector[self.index],yvector[self.index])
300 #-----------------------------------------
301 #CSV-HELPING FUNCTIONS        
302         
303 def transposed2(lists, defval=0):
304     '''
305     transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
306     elements
307     (by Zoran Isailovski on the Python Cookbook online)
308     '''
309     if not lists: return []
310     return map(lambda *row: [elem or defval for elem in row], *lists)
311         
312 def csv_write_dictionary(f, data, sorting='COLUMNS'):
313     '''
314     Writes a CSV file from a dictionary, with keys as first column or row
315     Keys are in "random" order.
316     
317     Keys should be strings
318     Values should be lists or other iterables
319     '''
320     keys=data.keys()
321     values=data.values()
322     t_values=transposed2(values)
323     writer=csv.writer(f)
324
325     if sorting=='COLUMNS':
326         writer.writerow(keys)
327         for item in t_values:
328             writer.writerow(item)
329         
330     if sorting=='ROWS':
331         print 'Not implemented!' #FIXME: implement it.
332
333
334 #-----------------------------------------        
335                     
336 def debug():
337     '''
338     debug stuff from latest rewrite of hooke_playlist.py
339     should be removed sooner or later (or substituted with new debug code!)
340     '''
341     confo=HookeConfig()
342     print confo.load_config('hooke.conf')
343
344 if __name__ == '__main__':
345     debug()