(fit.py) Continuing to sandbox stuff for better contact point algorithm
[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             outfile=file(output_filename,'w')
129             self.playlist.writexml(outfile,indent='\n')
130             outfile.close()
131
132
133 class HookeConfig:
134     '''
135     Handling of Hooke configuration file
136     
137     Mostly based on the simple-yet-useful examples of the Python Library Reference
138     about xml.dom.minidom
139     
140     FIXME: starting to look a mess, should require refactoring
141     '''
142     
143     def __init__(self):
144         self.config={}
145         self.config['plugins']=[]
146         self.config['drivers']=[]
147         self.config['plotmanips']=[]
148                 
149     def load_config(self, filename):
150         myconfig=file(filename)
151                     
152         #the following 3 lines are needed to strip newlines. otherwise, since newlines
153         #are XML elements too, the parser would read them (and re-save them, multiplying
154         #newlines...)
155         #yes, I'm an XML n00b
156         the_file=myconfig.read()
157         the_file_lines=the_file.split('\n')
158         the_file=''.join(the_file_lines)
159                        
160         self.config_tree=xml.dom.minidom.parseString(the_file)  
161         
162         def getText(nodelist):
163             #take the text from a nodelist
164             #from Python Library Reference 13.7.2
165             rc = ''
166             for node in nodelist:
167                 if node.nodeType == node.TEXT_NODE:
168                     rc += node.data
169             return rc
170         
171         def handleConfig(config):
172             display_elements=config.getElementsByTagName("display")
173             plugins_elements=config.getElementsByTagName("plugins")
174             drivers_elements=config.getElementsByTagName("drivers")
175             workdir_elements=config.getElementsByTagName("workdir")
176             defaultlist_elements=config.getElementsByTagName("defaultlist")
177             plotmanip_elements=config.getElementsByTagName("plotmanips")
178             handleDisplay(display_elements)
179             handlePlugins(plugins_elements)
180             handleDrivers(drivers_elements)
181             handleWorkdir(workdir_elements)
182             handleDefaultlist(defaultlist_elements)
183             handlePlotmanip(plotmanip_elements)
184             
185         def handleDisplay(display_elements):
186             for element in display_elements:
187                 for attribute in element.attributes.keys():
188                     self.config[attribute]=element.getAttribute(attribute)
189                     
190         def handlePlugins(plugins):
191             for plugin in plugins[0].childNodes:
192                 try:
193                     self.config['plugins'].append(str(plugin.tagName))
194                 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
195                     pass
196         #FIXME: code duplication
197         def handleDrivers(drivers):
198             for driver in drivers[0].childNodes:
199                 try:
200                     self.config['drivers'].append(str(driver.tagName))
201                 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
202                     pass
203         
204         def handlePlotmanip(plotmanips):
205             for plotmanip in plotmanips[0].childNodes:
206                 try:
207                     self.config['plotmanips'].append(str(plotmanip.tagName))
208                 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
209                     pass
210         
211         def handleWorkdir(workdir):
212             '''
213             default working directory
214             '''
215             wdir=getText(workdir[0].childNodes)
216             self.config['workdir']=wdir.strip()
217             
218         def handleDefaultlist(defaultlist):
219             '''
220             default playlist
221             '''
222             dflist=getText(defaultlist[0].childNodes)
223             self.config['defaultlist']=dflist.strip()
224             
225         handleConfig(self.config_tree)
226         #making items in the dictionary more machine-readable
227         for item in self.config.keys():
228             try:
229                 self.config[item]=float(self.config[item])
230             except TypeError: #we are dealing with a list, probably. keep it this way.
231                 try:
232                     self.config[item]=eval(self.config[item])
233                 except: #not a list, not a tuple, probably a string?
234                     pass
235             except ValueError: #if we can't get it to a number, it must be None or a string
236                 if string.lower(self.config[item])=='none':
237                     self.config[item]=None
238                 else:
239                     pass
240                                                 
241         return self.config
242         
243         
244     def save_config(self, config_filename):
245         print 'Not Implemented.'
246         pass    
247
248
249 class ClickedPoint:
250     '''
251     this class defines what a clicked point on the curve plot is
252     '''
253     def __init__(self):
254         
255         self.is_marker=None #boolean ; decides if it is a marker
256         self.is_line_edge=None #boolean ; decides if it is the edge of a line (unused)
257         self.absolute_coords=(None,None) #(float,float) ; the absolute coordinates of the clicked point on the graph
258         self.graph_coords=(None,None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point
259         self.index=None #integer ; the index of the clicked point with respect to the vector selected
260         self.dest=None #0 or 1 ; 0=top plot 1=bottom plot
261                 
262         
263     def find_graph_coords(self, xvector, yvector):
264         '''
265         Given a clicked point on the plot, finds the nearest point in the dataset (in X) that
266         corresponds to the clicked point.
267         '''
268                    
269         best_index=0
270         best_dist=10**9 #should be more than enough given the scale
271                 
272         for index in scipy.arange(1,len(xvector),1):
273             dist=((self.absolute_coords[0]-xvector[index])**2)+(100*((self.absolute_coords[1]-yvector[index])))**2
274                         #TODO, generalize? y coordinate is multiplied by 100 due to scale differences in the plot
275             if dist<best_dist:
276                 best_index=index
277                 best_dist=dist
278                         
279         self.index=best_index
280         self.graph_coords=(xvector[best_index],yvector[best_index])
281         return
282     
283 #-----------------------------------------
284 #CSV-HELPING FUNCTIONS        
285         
286 def transposed2(lists, defval=0):
287     '''
288     transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
289     elements
290     (by Zoran Isailovski on the Python Cookbook online)
291     '''
292     if not lists: return []
293     return map(lambda *row: [elem or defval for elem in row], *lists)
294         
295 def csv_write_dictionary(f, data, sorting='COLUMNS'):
296     '''
297     Writes a CSV file from a dictionary, with keys as first column or row
298     Keys are in "random" order.
299     
300     Keys should be strings
301     Values should be lists or other iterables
302     '''
303     keys=data.keys()
304     values=data.values()
305     t_values=transposed2(values)
306     writer=csv.writer(f)
307
308     if sorting=='COLUMNS':
309         writer.writerow(keys)
310         for item in t_values:
311             writer.writerow(item)
312         
313     if sorting=='ROWS':
314         print 'Not implemented!' #FIXME: implement it.
315
316
317 #-----------------------------------------        
318                     
319 def debug():
320     '''
321     debug stuff from latest rewrite of hooke_playlist.py
322     should be removed sooner or later (or substituted with new debug code!)
323     '''
324     confo=HookeConfig()
325     print confo.load_config('hooke.conf')
326
327 if __name__ == '__main__':
328     debug()