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