6 Command line module of Hooke.
8 Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy).
10 This program is released under the GNU General Public License version 2.
14 from libhooke import * #FIXME
15 import libhookecurve as lhc
17 from libhooke import WX_GOOD
18 from libhooke import HOOKE_VERSION
21 wxversion.select(WX_GOOD)
24 from wx.lib.newevent import NewEvent
25 from matplotlib.numerix import * #FIXME
27 import xml.dom.minidom
28 import sys, os, os.path, glob, shutil
35 global __releasedate__
36 __version__ = HOOKE_VERSION[0]
37 __codename__ = HOOKE_VERSION[1]
38 __releasedate__ = HOOKE_VERSION[2]
40 from matplotlib import __version__ as mpl_version
41 from wx import __version__ as wx_version
42 from wxmpl import __version__ as wxmpl_version
43 from scipy import __version__ as scipy_version
44 from numpy import __version__ as numpy_version
45 from sys import version as python_version
49 class HookeCli(cmd.Cmd):
51 def __init__(self,frame,list_of_events,events_from_gui,config,drivers):
52 cmd.Cmd.__init__(self)
54 self.prompt = 'hooke: '
57 self.current_list=[] #the playlist we're using
59 self.current=None #the current curve under analysis.
62 The actual hierarchy of the "current curve" is a bit complex:
64 self.current = the lhc.HookeCurve container object of the current curve
65 self.current.curve = the current "real" curve object as defined in the filetype driver class
66 self.current.curve.default_plots() = the default plots of the filetype driver.
68 The plot objects obtained by mean of self.current.curve.default_plots()
69 then undergoes modifications by the plotmanip
70 modifier functions. The modified plot is saved in self.plots and used if needed by other functions.
74 self.pointer=0 #a pointer to navigate the current list
76 #Things that come from outside
77 self.frame=frame #the wx frame we refer to
78 self.list_of_events=list_of_events #a list of wx events we use to interact with the GUI
79 self.events_from_gui=events_from_gui #the Queue object we use to have messages from the GUI
80 self.config=config #the configuration dictionary
81 self.drivers=drivers #the file format drivers
83 #get plot manipulation functions
84 plotmanip_functions=[]
85 for object_name in dir(self):
86 if object_name[0:9]=='plotmanip':
87 plotmanip_functions.append(getattr(self,object_name))
88 #put plotmanips in order
89 self.plotmanip=[None for item in self.config['plotmanips']]
90 for item in plotmanip_functions:
91 namefunction=item.__name__[10:]
92 if namefunction in self.config['plotmanips']:
93 nameindex=self.config['plotmanips'].index(namefunction) #index of function in plotmanips config
94 self.plotmanip[nameindex] = item
100 self.playlist_name=''
103 #Data that must be saved in the playlist, related to the whole playlist (not individual curves)
104 self.playlist_generics={}
106 #make sure we execute _plug_init() for every command line plugin we import
107 for plugin_name in self.config['plugins']:
109 plugin=__import__(plugin_name)
111 eval('plugin.'+plugin_name+'Commands._plug_init(self)')
112 except AttributeError:
119 #Everything sending an event should be here
120 def _measure_N_points(self, N, whatset=1):
122 general helper function for N-points measures
124 measure_points=self.list_of_events['measure_points']
125 wx.PostEvent(self.frame, measure_points(num_of_points=N, set=whatset))
128 points=self.frame.events_from_gui.get()
134 def _get_displayed_plot(self,dest=0):
136 returns the currently displayed plot.
138 wx.PostEvent(self.frame, self.list_of_events['get_displayed_plot'](dest=dest))
141 displayed_plot=self.events_from_gui.get()
146 return displayed_plot
148 def _send_plot(self,plots):
150 sends a plot to the GUI
152 wx.PostEvent(self.frame, self.list_of_events['plot_graph'](plots=plots))
155 def _find_plotmanip(self, name):
157 returns a plot manipulator function from its name
159 return self.plotmanip[self.config['plotmanips'].index(name)]
166 Sets a local configuration variable
168 Syntax: set [variable] [value]
170 def do_set(self,args):
171 #FIXME: some variables in self.config should be hidden or intelligently configurated...
174 print 'You must specify a variable and a value'
175 print 'Available variables:'
176 print self.config.keys()
178 if args[0] not in self.config.keys():
179 print 'This is not an internal Hooke variable!'
182 #FIXME:we should reload the config file and reset the config value
183 print self.config[args[0]]
186 try: #try to have a numeric value
188 except ValueError: #if it cannot be converted to float, it's None, or a string...
189 if value.lower()=='none':
194 self.config[key]=value
197 #PLAYLIST MANAGEMENT AND NAVIGATION
198 #------------------------------------
200 def help_loadlist(self):
203 Loads a file playlist
205 Syntax: loadlist [playlist file]
207 def do_loadlist(self, args):
208 #checking for args: if nothing is given as input, we warn and exit.
210 args=raw_input('File to load?')
213 play_to_load=arglist[0]
215 #We assume a Hooke playlist has the extension .hkp
216 if play_to_load[-4:] != '.hkp':
220 playxml=PlaylistXML()
221 self.current_list, self.playlist_generics=playxml.load(play_to_load)
222 self.current_playxml=playxml
224 print 'File not found.'
227 print 'Loaded %s curves' %len(self.current_list)
229 if 'pointer' in self.playlist_generics.keys():
230 self.pointer=int(self.playlist_generics['pointer'])
232 #if no pointer is found, set the current curve as the first curve of the loaded playlist
234 print 'Starting at curve ',self.pointer
236 self.current=self.current_list[self.pointer]
238 #resets saved/notes saved state
239 self.playlist_saved=0
240 self.playlist_name=''
246 def help_genlist(self):
249 Generates a file playlist.
250 Note it doesn't *save* it: see savelist for this.
252 If [input files] is a directory, it will use all files in the directory for playlist.
258 are all equivalent syntax.
260 Syntax: genlist [input files]
263 def do_genlist(self,args):
264 #args list is: input path, output name
266 args=raw_input('Input files?')
271 #if it's a directory, is like /directory/*.*
272 #FIXME: probably a bit kludgy.
273 if os.path.isdir(list_path):
274 if platform.system == 'Windows':
278 if list_path[-1] == SLASH:
279 list_path=list_path+'*.*'
281 list_path=list_path+SLASH+'*.*'
283 #expanding correctly the input list with the glob module :)
284 list_files=glob.glob(list_path)
288 for item in list_files:
290 self.current_list.append(lhc.HookeCurve(os.path.abspath(item)))
295 if len(self.current_list)>0:
296 self.current=self.current_list[self.pointer]
301 #resets saved/notes saved state
302 self.playlist_saved=0
303 self.playlist_name=''
309 def do_savelist(self,args):
312 Saves the current file playlist on disk.
314 Syntax: savelist [filename]
317 args=raw_input('Input files?')
321 self.playlist_generics['pointer']=self.pointer
323 #autocomplete filename if not specified
324 if output_filename[-4:] != '.hkp':
325 output_filename+='.hkp'
327 playxml=PlaylistXML()
328 playxml.export(self.current_list, self.playlist_generics)
329 playxml.save(output_filename)
331 #remembers we have saved playlist
332 self.playlist_saved=1
334 def help_addtolist(self):
337 Adds a file to the current playlist
339 Syntax: addtolist [filename]
341 def do_addtolist(self,args):
342 #args list is: input path
344 print 'You must give the input filename you want to add'
345 self.help_addtolist()
348 filenames=glob.glob(args)
350 for filename in filenames:
351 self.current_list.append(lhc.HookeCurve(os.path.abspath(filename)))
352 #we need to save playlist
353 self.playlist_saved=0
355 def help_printlist(self):
358 Prints the list of curves in the current playlist
362 def do_printlist(self,args):
363 for item in self.current_list:
370 Jumps to a given curve.
372 Syntax: jump {$curve}
374 If the curve is not in the current playlist, it politely asks if we want to add it.
376 def do_jump(self,filename):
378 jumps to the curve with the given filename.
379 if the filename is not in the playlist, it asks if we must add it or not.
383 filename=raw_input('Jump to?')
385 filepath=os.path.abspath(filename)
390 while item_not_found:
393 if self.current_list[c].path == filepath:
395 self.current=self.current_list[self.pointer]
401 #We've found the end of the list.
402 answer=raw_input('Curve not found in playlist. Add it to list?')
403 if answer.lower()[0]=='y':
405 self.do_addtolist(filepath)
407 print 'Curve file not found.'
409 self.current=self.current_list[-1]
410 self.pointer=(len(current_list)-1)
416 def do_index(self,args):
419 Prints the index of the current curve in the list
423 print self.pointer+1, 'of', len(self.current_list)
429 Go the next curve in the playlist.
430 If we are at the last curve, we come back to the first.
434 def do_next(self,args):
435 self.current.curve.close_all()
436 if self.pointer == (len(self.current_list)-1):
438 print 'Playlist finished; back to first curve.'
442 self.current=self.current_list[self.pointer]
451 def help_previous(self,args):
454 Go to the previous curve in the playlist.
455 If we are at the first curve, we jump to the last.
459 def do_previous(self,args):
460 self.current.curve.close_all()
461 if self.pointer == 0:
462 self.pointer=(len(self.current_list)-1)
463 print 'Start of playlist; jump to last curve.'
467 self.current=self.current_list[self.pointer]
474 self.do_previous(args)
477 #PLOT INTERACTION COMMANDS
478 #-------------------------------
482 Plots the current force curve
486 def do_plot(self,args):
488 self.current.identify(self.drivers)
489 self.plots=self.current.curve.default_plots()
491 self.plots=self.current.curve.default_plots()
493 print 'Unexpected error occurred in do_plot().'
497 #apply the plotmanip functions eventually present
498 nplots=len(self.plots)
501 for function in self.plotmanip: #FIXME: something strange happens about self.plotmanip[0]
502 self.plots[c]=function(self.plots[c], self.current)
504 self.plots[c].xaxes=self.config['xaxes'] #FIXME: in the future, xaxes and yaxes should be set per-plot
505 self.plots[c].yaxes=self.config['yaxes']
509 self._send_plot(self.plots)
511 def _delta(self, set=1):
513 calculates the difference between two clicked points
515 print 'Click two points'
516 points=self._measure_N_points(N=2, whatset=set)
517 dx=abs(points[0].graph_coords[0]-points[1].graph_coords[0])
518 dy=abs(points[0].graph_coords[1]-points[1].graph_coords[1])
519 unitx=self.plots[points[0].dest].units[0]
520 unity=self.plots[points[0].dest].units[1]
521 return dx,unitx,dy,unity
523 def do_delta(self,args):
527 Measures the delta X and delta Y between two points.
531 dx,unitx,dy,unity=self._delta()
532 print str(dx)+' '+unitx
533 print str(dy)+' '+unity
535 def _point(self, set=1):
536 '''calculates the coordinates of a single clicked point'''
538 print 'Click one point'
539 point=self._measure_N_points(N=1, whatset=set)
541 x=point[0].graph_coords[0]
542 y=point[0].graph_coords[1]
543 unitx=self.plots[point[0].dest].units[0]
544 unity=self.plots[point[0].dest].units[1]
545 return x,unitx,y,unity
547 def do_point(self,args):
551 Returns the coordinates of a point on the graph.
555 x,unitx,y,unity=self._point()
556 print str(x)+' '+unitx
557 print str(y)+' '+unity
561 def do_close(self,args=None):
564 Closes one of the two plots. If no arguments are given, the bottom plot is closed.
566 Syntax: close [top,bottom]
575 close_plot=self.list_of_events['close_plot']
576 wx.PostEvent(self.frame, close_plot(to_close=to_close))
578 def do_show(self,args=None):
583 show_plots=self.list_of_events['show_plots']
584 wx.PostEvent(self.frame, show_plots())
588 #PLOT EXPORT AND MANIPULATION COMMANDS
589 def help_export(self):
592 Saves the current plot as an image file
594 Syntax: export [filename] {plot to export}
596 The supported formats are PNG and EPS; the file extension of the filename is automatically recognized
597 and correctly exported. Resolution is (for now) fixed at 150 dpi.
599 If you have a multiple plot, the optional plot to export argument tells Hooke which plot you want to export. If 0, the top plot is exported. If 1, the bottom plot is exported (Exporting both plots is still to implement)
601 def do_export(self,args):
602 #FIXME: the bottom plot doesn't have the title
606 name=raw_input('Filename?')
613 export_image=self.list_of_events['export_image']
614 wx.PostEvent(self.frame, export_image(name=name, dest=dest))
620 Saves the current curve as a text file
621 Columns are, in order:
622 X1 , Y1 , X2 , Y2 , X3 , Y3 ...
625 Syntax: txt [filename] {plot to export}
627 def do_txt(self,args):
629 def transposed2(lists, defval=0):
631 transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
633 (by Zoran Isailovski on the Python Cookbook online)
635 if not lists: return []
636 return map(lambda *row: [elem or defval for elem in row], *lists)
641 filename=raw_input('Filename?')
645 whichplot=int(args[1])
650 for dataset in self.plots[whichplot].vectors:
651 for i in range(0,len(dataset)):
653 for value in dataset[i]:
654 columns[-1].append(str(value))
656 rows=transposed2(columns, 'nan')
657 rows=[' , '.join(item) for item in rows]
660 txtfile=open(filename,'w+')
665 #LOGGING, REPORTING, NOTETAKING
669 Writes or displays a note about the current curve.
670 If [anything] is empty, it displays the note, otherwise it adds a note.
671 The note is then saved in the playlist if you issue a savelist command
673 Syntax: note [anything]
675 def do_note(self,args):
677 print self.current_list[self.pointer].notes
679 #bypass UnicodeDecodeError troubles
681 args=args.decode('ascii')
683 args=args.decode('ascii','ignore')
687 self.current_list[self.pointer].notes=args
690 def help_notelog(self):
693 Writes a log of the notes taken during the session for the current
696 Syntax notelog [filename]
698 def do_notelog(self,args):
701 args=raw_input('Notelog filename?')
703 note_lines='Notes taken at '+time.asctime()+'\n'
704 for item in self.current_list:
705 if len(item.notes)>0:
706 #FIXME: log should be justified
707 #FIXME: file path should be truncated...
708 note_string=(item.path+' | '+item.notes+'\n')
709 note_lines+=note_string
715 except IOError, (ErrorNumber, ErrorMessage):
716 print 'Error: notes cannot be saved. Catched exception:'
721 def help_copylog(self):
724 Moves the annotated curves to another directory
726 Syntax copylog [directory]
728 def do_copylog(self,args):
731 args=raw_input('Destination directory?')
733 mydir=os.path.abspath(args)
734 if not os.path.isdir(mydir):
735 print 'Destination is not a directory.'
738 for item in self.current_list:
739 if len(item.notes)>0:
741 shutil.copy(item.path, mydir)
743 print 'OSError. Cannot copy file. Perhaps you gave me a wrong directory?'
746 #OS INTERACTION COMMANDS
751 Lists the files in the directory
756 def do_dir(self,args):
760 print glob.glob(args)
764 def do_ls(self,args):
770 Gives the current working directory.
774 def do_pwd(self,args):
780 Changes the current working directory
784 def do_cd(self,args):
785 mypath=os.path.abspath(args)
789 print 'I cannot access that directory.'
792 def help_system(self):
795 Executes a system command line and reports the output
797 Syntax system [command line]
800 def do_system(self,args):
801 waste=os.system(args)
803 def do_debug(self,args):
805 this is a dummy command where I put debugging things
807 print self.config['plotmanips']
810 def help_current(self):
813 Prints the current curve path.
817 def do_current(self,args):
818 print self.current.path
820 def do_version(self,args):
824 Prints the current version and codename, plus library version. Useful for debugging.
826 print 'Hooke '+__version__+' ('+__codename__+')'
827 print 'Released on: '+__releasedate__
829 print 'Python version: '+python_version
830 print 'WxPython version: '+wx_version
831 print 'wxMPL version: '+wxmpl_version
832 print 'Matplotlib version: '+mpl_version
833 print 'SciPy version: '+scipy_version
834 print 'NumPy version: '+numpy_version
836 print 'Platform: '+str(platform.uname())
838 print 'Loaded plugins:',self.config['loaded_plugins']
843 Exits the program cleanly.
848 def do_exit(self,args):
851 if (not self.playlist_saved) or (not self.notes_saved):
852 we_exit=raw_input('You did not save your playlist and/or notes. Exit?')
854 we_exit=raw_input('Exit?')
856 if we_exit[0].upper()=='Y':
857 wx.CallAfter(self.frame.Close)
864 def do_quit(self,args):
869 if __name__ == '__main__':