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