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 xml.dom.minidom
22 from matplotlib.ticker import ScalarFormatter
25 from . import libhookecurve as lhc
27 HOOKE_VERSION=['0.8.3_devel', 'Seinei', '2008-04-16']
30 class PlaylistXML(object):
32 This module allows for import/export of an XML playlist into/out of a list of HookeCurve objects
37 self.playlist=None #the DOM object representing the playlist data structure
38 self.playpath=None #the path of the playlist XML file
40 self.hidden_attributes=['curve'] #This list contains hidden attributes that we don't want to go into the playlist.
42 def export(self, list_of_hooke_curves, generics):
44 Creates an initial playlist from a list of files.
45 A playlist is an XML document with the following syntaxis:
47 <element path="/my/file/path/"/ attribute="attribute">
52 #create the output playlist, a simple XML document
53 impl=xml.dom.minidom.getDOMImplementation()
54 #create the document DOM object and the root element
55 newdoc=impl.createDocument(None, "playlist",None)
56 top_element=newdoc.documentElement
58 #save generics variables
59 playlist_generics=newdoc.createElement("generics")
60 top_element.appendChild(playlist_generics)
61 for key in generics.keys():
62 newdoc.createAttribute(key)
63 playlist_generics.setAttribute(key,str(generics[key]))
65 #save curves and their attributes
66 for item in list_of_hooke_curves:
67 #playlist_element=newdoc.createElement("curve")
68 playlist_element=newdoc.createElement("element")
69 top_element.appendChild(playlist_element)
70 for key in item.__dict__:
71 if not (key in self.hidden_attributes):
72 newdoc.createAttribute(key)
73 playlist_element.setAttribute(key,str(item.__dict__[key]))
77 def load(self,filename):
82 self.playpath=filename
84 #the following 3 lines are needed to strip newlines. otherwise, since newlines
85 #are XML elements too (why?), the parser would read them (and re-save them, multiplying
88 the_file=myplay.read()
89 the_file_lines=the_file.split('\n')
90 the_file=''.join(the_file_lines)
92 self.playlist=xml.dom.minidom.parseString(the_file)
94 #inner parsing functions
95 def handlePlaylist(playlist):
96 list_of_files=playlist.getElementsByTagName("element")
97 generics=playlist.getElementsByTagName("generics")
98 return handleFiles(list_of_files), handleGenerics(generics)
100 def handleGenerics(generics):
105 for attribute in generics[0].attributes.keys():
106 generics_dict[attribute]=generics[0].getAttribute(attribute)
109 def handleFiles(list_of_files):
111 for myfile in list_of_files:
112 #rebuild a data structure from the xml attributes
113 the_curve=lhc.HookeCurve(
114 os.path.join(os.path.dirname(self.playpath),
115 myfile.getAttribute('path')))
116 for attribute in myfile.attributes.keys():
117 #extract attributes for the single curve
118 if attribute == 'path':
119 continue # we already added this attribute
120 the_curve.__dict__[attribute]=myfile.getAttribute(attribute)
121 new_playlist.append(the_curve)
123 return new_playlist #this is the true thing returned at the end of this function...(FIXME: clarity)
125 return handlePlaylist(self.playlist)
128 def save(self,output_filename):
130 saves the playlist in a XML file.
133 outfile=file(output_filename,'w')
135 print 'libhooke.py : Cannot save playlist. Wrong path or filename'
138 self.playlist.writexml(outfile,indent='\n')
141 def config_file_path(filename, config_dir=None):
142 if config_dir == None:
143 config_dir = os.path.abspath(
144 os.path.join(os.path.dirname(os.path.dirname(__file__)), 'conf'))
145 return os.path.join(config_dir, filename)
147 class HookeConfig(object):
149 Handling of Hooke configuration file
151 Mostly based on the simple-yet-useful examples of the Python Library Reference
152 about xml.dom.minidom
154 FIXME: starting to look a mess, should require refactoring
157 def __init__(self, config_dir=None):
159 self.config['install']={}
160 self.config['plugins']=[]
161 self.config['drivers']=[]
162 self.config['plotmanips']=[]
163 self.config_dir = config_dir
165 def load_config(self, filename):
166 myconfig=file(config_file_path(filename, config_dir=self.config_dir))
168 #the following 3 lines are needed to strip newlines. otherwise, since newlines
169 #are XML elements too, the parser would read them (and re-save them, multiplying
171 #yes, I'm an XML n00b
172 the_file=myconfig.read()
173 the_file_lines=the_file.split('\n')
174 the_file=''.join(the_file_lines)
176 self.config_tree=xml.dom.minidom.parseString(the_file)
178 def getText(nodelist):
179 #take the text from a nodelist
180 #from Python Library Reference 13.7.2
182 for node in nodelist:
183 if node.nodeType == node.TEXT_NODE:
187 def handleConfig(config):
188 install_elements=config.getElementsByTagName("install")
189 display_elements=config.getElementsByTagName("display")
190 plugins_elements=config.getElementsByTagName("plugins")
191 drivers_elements=config.getElementsByTagName("drivers")
192 defaultlist_elements=config.getElementsByTagName("defaultlist")
193 plotmanip_elements=config.getElementsByTagName("plotmanips")
194 handleInstall(install_elements)
195 handleDisplay(display_elements)
196 handlePlugins(plugins_elements)
197 handleDrivers(drivers_elements)
198 handleDefaultlist(defaultlist_elements)
199 handlePlotmanip(plotmanip_elements)
201 def handleInstall(install_elements):
202 for install in install_elements:
203 for node in install.childNodes:
204 if node.nodeType == node.TEXT_NODE:
206 path = os.path.abspath(getText(node.childNodes).strip())
207 self.config['install'][str(node.tagName)] = path
209 def handleDisplay(display_elements):
210 for element in display_elements:
211 for attribute in element.attributes.keys():
212 self.config[attribute]=element.getAttribute(attribute)
214 def handlePlugins(plugins):
215 for plugin in plugins[0].childNodes:
217 self.config['plugins'].append(str(plugin.tagName))
218 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
220 #FIXME: code duplication
221 def handleDrivers(drivers):
222 for driver in drivers[0].childNodes:
224 self.config['drivers'].append(str(driver.tagName))
225 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
228 def handlePlotmanip(plotmanips):
229 for plotmanip in plotmanips[0].childNodes:
231 self.config['plotmanips'].append(str(plotmanip.tagName))
232 except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it...
235 def handleDefaultlist(defaultlist):
239 dflist=getText(defaultlist[0].childNodes)
240 self.config['defaultlist']=dflist.strip()
242 handleConfig(self.config_tree)
243 #making items in the dictionary more machine-readable
244 for item in self.config.keys():
246 self.config[item]=float(self.config[item])
247 except TypeError: #we are dealing with a list, probably. keep it this way.
249 self.config[item]=eval(self.config[item])
250 except: #not a list, not a tuple, probably a string?
252 except ValueError: #if we can't get it to a number, it must be None or a string
253 if string.lower(self.config[item])=='none':
254 self.config[item]=None
261 def save_config(self, config_filename):
262 print 'Not Implemented.'
266 class EngrFormatter(ScalarFormatter):
267 """A variation of the standard ScalarFormatter, using only multiples of
269 in the mantissa. A fixed number of decimals can be displayed with the optional
270 parameter `ndec` . If `ndec` is None (default), the number of decimals is
272 from the current ticks.
274 def __init__(self, ndec=None, useOffset=True, useMathText=False):
275 ScalarFormatter.__init__(self, useOffset, useMathText)
276 if ndec is None or ndec < 0:
281 self.format = "%%1.%if" % ndec
282 #........................
284 def _set_orderOfMagnitude(self, mrange):
285 """Sets the order of magnitude."""
286 locs = numpy.absolute(self.locs)
288 oom = numpy.floor(numpy.log10(mrange))
290 if locs[0] > locs[-1]:
297 oom = numpy.floor(numpy.log10(val))
299 self.orderOfMagnitude = 3*(oom//3)
301 self.orderOfMagnitude = -3
303 self.orderOfMagnitude = 3*(oom//3)
305 self.orderOfMagnitude = 0
308 #........................
309 def _set_format(self):
310 """Sets the format string to format all ticklabels."""
311 # set the format string to format all the ticklabels
312 locs = (numpy.array(self.locs)-self.offset) / 10**self.orderOfMagnitude+1e-15
313 sigfigs = [len(str('%1.3f'% loc).split('.')[1].rstrip('0')) \
316 if self.format is None:
317 self.format = '%1.' + str(sigfigs[-1]) + 'f'
318 if self._usetex or self._useMathText: self.format = '$%s$'%self.format
324 this class defines what a clicked point on the curve plot is
328 self.is_marker=None #boolean ; decides if it is a marker
329 self.is_line_edge=None #boolean ; decides if it is the edge of a line (unused)
330 self.absolute_coords=(None,None) #(float,float) ; the absolute coordinates of the clicked point on the graph
331 self.graph_coords=(None,None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point
332 self.index=None #integer ; the index of the clicked point with respect to the vector selected
333 self.dest=None #0 or 1 ; 0=top plot 1=bottom plot
336 def find_graph_coords_old(self, xvector, yvector):
338 Given a clicked point on the plot, finds the nearest point in the dataset (in X) that
339 corresponds to the clicked point.
340 OLD & DEPRECATED - to be removed
343 #FIXME: a general algorithm using min() is needed!
344 #print '---DEPRECATED FIND_GRAPH_COORDS_OLD---'
346 best_dist=10**9 #should be more than enough given the scale
348 for index in scipy.arange(1,len(xvector),1):
349 dist=((self.absolute_coords[0]-xvector[index])**2)+(100*((self.absolute_coords[1]-yvector[index])))**2
350 #TODO, generalize? y coordinate is multiplied by 100 due to scale differences in the plot
355 self.index=best_index
356 self.graph_coords=(xvector[best_index],yvector[best_index])
359 def find_graph_coords(self,xvector,yvector):
361 Given a clicked point on the plot, finds the nearest point in the dataset that
362 corresponds to the clicked point.
365 for index in scipy.arange(1,len(xvector),1):
366 dists.append(((self.absolute_coords[0]-xvector[index])**2)+((self.absolute_coords[1]-yvector[index])**2))
368 self.index=dists.index(min(dists))
369 self.graph_coords=(xvector[self.index],yvector[self.index])
370 #-----------------------------------------
371 #CSV-HELPING FUNCTIONS
373 def transposed2(lists, defval=0):
375 transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
377 (by Zoran Isailovski on the Python Cookbook online)
379 if not lists: return []
380 return map(lambda *row: [elem or defval for elem in row], *lists)
382 def csv_write_dictionary(f, data, sorting='COLUMNS'):
384 Writes a CSV file from a dictionary, with keys as first column or row
385 Keys are in "random" order.
387 Keys should be strings
388 Values should be lists or other iterables
392 t_values=transposed2(values)
395 if sorting=='COLUMNS':
396 writer.writerow(keys)
397 for item in t_values:
398 writer.writerow(item)
401 print 'Not implemented!' #FIXME: implement it.
404 #-----------------------------------------
408 debug stuff from latest rewrite of hooke_playlist.py
409 should be removed sooner or later (or substituted with new debug code!)
412 print confo.load_config('hooke.conf')
414 if __name__ == '__main__':