2 # -*- coding: utf-8 -*-
7 General library of internal objects and utilities for Hooke.
9 Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy).
10 With algorithms contributed by Francesco Musiani (University of Bologna, Italy)
12 This program is released under the GNU General Public License version 2.
17 import libhookecurve as lhc
24 import xml.dom.minidom
28 from matplotlib.ticker import ScalarFormatter
31 HOOKE_VERSION=['0.8.3_devel', 'Seinei', '2008-04-16']
36 This module allows for import/export of an XML playlist into/out of a list of HookeCurve objects
41 self.playlist=None #the DOM object representing the playlist data structure
42 self.playpath=None #the path of the playlist XML file
44 self.hidden_attributes=['curve'] #This list contains hidden attributes that we don't want to go into the playlist.
46 def export(self, list_of_hooke_curves, generics):
48 Creates an initial playlist from a list of files.
49 A playlist is an XML document with the following syntaxis:
51 <element path="/my/file/path/"/ attribute="attribute">
56 #create the output playlist, a simple XML document
57 impl=xml.dom.minidom.getDOMImplementation()
58 #create the document DOM object and the root element
59 newdoc=impl.createDocument(None, "playlist",None)
60 top_element=newdoc.documentElement
62 #save generics variables
63 playlist_generics=newdoc.createElement("generics")
64 top_element.appendChild(playlist_generics)
65 for key in generics.keys():
66 newdoc.createAttribute(key)
67 playlist_generics.setAttribute(key,str(generics[key]))
69 #save curves and their attributes
70 for item in list_of_hooke_curves:
71 #playlist_element=newdoc.createElement("curve")
72 playlist_element=newdoc.createElement("element")
73 top_element.appendChild(playlist_element)
74 for key in item.__dict__:
75 if not (key in self.hidden_attributes):
76 newdoc.createAttribute(key)
77 playlist_element.setAttribute(key,str(item.__dict__[key]))
81 def load(self,filename):
86 self.playpath=filename
88 #the following 3 lines are needed to strip newlines. otherwise, since newlines
89 #are XML elements too (why?), the parser would read them (and re-save them, multiplying
92 the_file=myplay.read()
93 the_file_lines=the_file.split('\n')
94 the_file=''.join(the_file_lines)
96 self.playlist=xml.dom.minidom.parseString(the_file)
98 #inner parsing functions
99 def handlePlaylist(playlist):
100 list_of_files=playlist.getElementsByTagName("element")
101 generics=playlist.getElementsByTagName("generics")
102 return handleFiles(list_of_files), handleGenerics(generics)
104 def handleGenerics(generics):
109 for attribute in generics[0].attributes.keys():
110 generics_dict[attribute]=generics[0].getAttribute(attribute)
113 def handleFiles(list_of_files):
115 for myfile in list_of_files:
116 #rebuild a data structure from the xml attributes
117 the_curve=lhc.HookeCurve(myfile.getAttribute('path'))
118 for attribute in myfile.attributes.keys(): #extract attributes for the single curve
119 the_curve.__dict__[attribute]=myfile.getAttribute(attribute)
120 new_playlist.append(the_curve)
122 return new_playlist #this is the true thing returned at the end of this function...(FIXME: clarity)
124 return handlePlaylist(self.playlist)
127 def save(self,output_filename):
129 saves the playlist in a XML file.
132 outfile=file(output_filename,'w')
134 print 'libhooke.py : Cannot save playlist. Wrong path or filename'
137 self.playlist.writexml(outfile,indent='\n')
143 Handling of Hooke configuration file
145 Mostly based on the simple-yet-useful examples of the Python Library Reference
146 about xml.dom.minidom
148 FIXME: starting to look a mess, should require refactoring
153 self.config['plugins']=[]
154 self.config['drivers']=[]
155 self.config['plotmanips']=[]
157 def load_config(self, filename):
158 myconfig=file(filename)
160 #the following 3 lines are needed to strip newlines. otherwise, since newlines
161 #are XML elements too, the parser would read them (and re-save them, multiplying
163 #yes, I'm an XML n00b
164 the_file=myconfig.read()
165 the_file_lines=the_file.split('\n')
166 the_file=''.join(the_file_lines)
168 self.config_tree=xml.dom.minidom.parseString(the_file)
170 def getText(nodelist):
171 #take the text from a nodelist
172 #from Python Library Reference 13.7.2
174 for node in nodelist:
175 if node.nodeType == node.TEXT_NODE:
179 def handleConfig(config):
180 display_elements=config.getElementsByTagName("display")
181 plugins_elements=config.getElementsByTagName("plugins")
182 drivers_elements=config.getElementsByTagName("drivers")
183 workdir_elements=config.getElementsByTagName("workdir")
184 defaultlist_elements=config.getElementsByTagName("defaultlist")
185 plotmanip_elements=config.getElementsByTagName("plotmanips")
186 handleDisplay(display_elements)
187 handlePlugins(plugins_elements)
188 handleDrivers(drivers_elements)
189 handleWorkdir(workdir_elements)
190 handleDefaultlist(defaultlist_elements)
191 handlePlotmanip(plotmanip_elements)
193 def handleDisplay(display_elements):
194 for element in display_elements:
195 for attribute in element.attributes.keys():
196 self.config[attribute]=element.getAttribute(attribute)
198 def handlePlugins(plugins):
199 for plugin in plugins[0].childNodes:
201 self.config['plugins'].append(str(plugin.tagName))
202 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
204 #FIXME: code duplication
205 def handleDrivers(drivers):
206 for driver in drivers[0].childNodes:
208 self.config['drivers'].append(str(driver.tagName))
209 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
212 def handlePlotmanip(plotmanips):
213 for plotmanip in plotmanips[0].childNodes:
215 self.config['plotmanips'].append(str(plotmanip.tagName))
216 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
219 def handleWorkdir(workdir):
221 default working directory
223 wdir=getText(workdir[0].childNodes)
224 self.config['workdir']=wdir.strip()
226 def handleDefaultlist(defaultlist):
230 dflist=getText(defaultlist[0].childNodes)
231 self.config['defaultlist']=dflist.strip()
233 handleConfig(self.config_tree)
234 #making items in the dictionary more machine-readable
235 for item in self.config.keys():
237 self.config[item]=float(self.config[item])
238 except TypeError: #we are dealing with a list, probably. keep it this way.
240 self.config[item]=eval(self.config[item])
241 except: #not a list, not a tuple, probably a string?
243 except ValueError: #if we can't get it to a number, it must be None or a string
244 if string.lower(self.config[item])=='none':
245 self.config[item]=None
252 def save_config(self, config_filename):
253 print 'Not Implemented.'
257 class EngrFormatter(ScalarFormatter):
258 """A variation of the standard ScalarFormatter, using only multiples of
260 in the mantissa. A fixed number of decimals can be displayed with the optional
261 parameter `ndec` . If `ndec` is None (default), the number of decimals is
263 from the current ticks.
265 def __init__(self, ndec=None, useOffset=True, useMathText=False):
266 ScalarFormatter.__init__(self, useOffset, useMathText)
267 if ndec is None or ndec < 0:
272 self.format = "%%1.%if" % ndec
273 #........................
275 def _set_orderOfMagnitude(self, mrange):
276 """Sets the order of magnitude."""
277 locs = numpy.absolute(self.locs)
279 oom = numpy.floor(numpy.log10(mrange))
281 if locs[0] > locs[-1]:
288 oom = numpy.floor(numpy.log10(val))
290 self.orderOfMagnitude = 3*(oom//3)
292 self.orderOfMagnitude = -3
294 self.orderOfMagnitude = 3*(oom//3)
296 self.orderOfMagnitude = 0
299 #........................
300 def _set_format(self):
301 """Sets the format string to format all ticklabels."""
302 # set the format string to format all the ticklabels
303 locs = (numpy.array(self.locs)-self.offset) / 10**self.orderOfMagnitude+1e-15
304 sigfigs = [len(str('%1.3f'% loc).split('.')[1].rstrip('0')) \
307 if self.format is None:
308 self.format = '%1.' + str(sigfigs[-1]) + 'f'
309 if self._usetex or self._useMathText: self.format = '$%s$'%self.format
315 this class defines what a clicked point on the curve plot is
319 self.is_marker=None #boolean ; decides if it is a marker
320 self.is_line_edge=None #boolean ; decides if it is the edge of a line (unused)
321 self.absolute_coords=(None,None) #(float,float) ; the absolute coordinates of the clicked point on the graph
322 self.graph_coords=(None,None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point
323 self.index=None #integer ; the index of the clicked point with respect to the vector selected
324 self.dest=None #0 or 1 ; 0=top plot 1=bottom plot
327 def find_graph_coords_old(self, xvector, yvector):
329 Given a clicked point on the plot, finds the nearest point in the dataset (in X) that
330 corresponds to the clicked point.
331 OLD & DEPRECATED - to be removed
334 #FIXME: a general algorithm using min() is needed!
335 #print '---DEPRECATED FIND_GRAPH_COORDS_OLD---'
337 best_dist=10**9 #should be more than enough given the scale
339 for index in scipy.arange(1,len(xvector),1):
340 dist=((self.absolute_coords[0]-xvector[index])**2)+(100*((self.absolute_coords[1]-yvector[index])))**2
341 #TODO, generalize? y coordinate is multiplied by 100 due to scale differences in the plot
346 self.index=best_index
347 self.graph_coords=(xvector[best_index],yvector[best_index])
350 def find_graph_coords(self,xvector,yvector):
352 Given a clicked point on the plot, finds the nearest point in the dataset that
353 corresponds to the clicked point.
356 for index in scipy.arange(1,len(xvector),1):
357 dists.append(((self.absolute_coords[0]-xvector[index])**2)+((self.absolute_coords[1]-yvector[index])**2))
359 self.index=dists.index(min(dists))
360 self.graph_coords=(xvector[self.index],yvector[self.index])
361 #-----------------------------------------
362 #CSV-HELPING FUNCTIONS
364 def transposed2(lists, defval=0):
366 transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
368 (by Zoran Isailovski on the Python Cookbook online)
370 if not lists: return []
371 return map(lambda *row: [elem or defval for elem in row], *lists)
373 def csv_write_dictionary(f, data, sorting='COLUMNS'):
375 Writes a CSV file from a dictionary, with keys as first column or row
376 Keys are in "random" order.
378 Keys should be strings
379 Values should be lists or other iterables
383 t_values=transposed2(values)
386 if sorting=='COLUMNS':
387 writer.writerow(keys)
388 for item in t_values:
389 writer.writerow(item)
392 print 'Not implemented!' #FIXME: implement it.
395 #-----------------------------------------
399 debug stuff from latest rewrite of hooke_playlist.py
400 should be removed sooner or later (or substituted with new debug code!)
403 print confo.load_config('hooke.conf')
405 if __name__ == '__main__':