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 import libinput as linp
18 import liboutlet as lout
20 from libhooke import WX_GOOD
21 from libhooke import HOOKE_VERSION
24 wxversion.select(WX_GOOD)
27 from wx.lib.newevent import NewEvent
28 from matplotlib.numerix import * #FIXME
30 import xml.dom.minidom
31 import sys, os, os.path, glob, shutil
38 global __releasedate__
39 __version__ = HOOKE_VERSION[0]
40 __codename__ = HOOKE_VERSION[1]
41 __releasedate__ = HOOKE_VERSION[2]
43 from matplotlib import __version__ as mpl_version
44 from wx import __version__ as wx_version
45 from wxmpl import __version__ as wxmpl_version
46 from scipy import __version__ as scipy_version
47 from numpy import __version__ as numpy_version
48 from sys import version as python_version
52 class HookeCli(cmd.Cmd):
54 def __init__(self,frame,list_of_events,events_from_gui,config,drivers):
55 cmd.Cmd.__init__(self)
57 self.prompt = 'hooke: '
60 self.current_list=[] #the playlist we're using
62 self.current=None #the current curve under analysis.
65 The actual hierarchy of the "current curve" is a bit complex:
67 self.current = the lhc.HookeCurve container object of the current curve
68 self.current.curve = the current "real" curve object as defined in the filetype driver class
69 self.current.curve.default_plots() = the default plots of the filetype driver.
71 The plot objects obtained by mean of self.current.curve.default_plots()
72 then undergoes modifications by the plotmanip
73 modifier functions. The modified plot is saved in self.plots and used if needed by other functions.
77 self.pointer=0 #a pointer to navigate the current list
79 #Things that come from outside
80 self.frame=frame #the wx frame we refer to
81 self.list_of_events=list_of_events #a list of wx events we use to interact with the GUI
82 self.events_from_gui=events_from_gui #the Queue object we use to have messages from the GUI
83 self.config=config #the configuration dictionary
84 self.drivers=drivers #the file format drivers
86 #get plot manipulation functions
87 plotmanip_functions=[]
88 for object_name in dir(self):
89 if object_name[0:9]=='plotmanip':
90 plotmanip_functions.append(getattr(self,object_name))
91 #put plotmanips in order
92 self.plotmanip=[None for item in self.config['plotmanips']]
93 for item in plotmanip_functions:
94 namefunction=item.__name__[10:]
95 if namefunction in self.config['plotmanips']:
96 nameindex=self.config['plotmanips'].index(namefunction) #index of function in plotmanips config
97 self.plotmanip[nameindex] = item
102 self.playlist_saved=0
103 self.playlist_name=''
107 self.outlet=lout.Outlet()
109 #Data that must be saved in the playlist, related to the whole playlist (not individual curves)
110 self.playlist_generics={}
112 #make sure we execute _plug_init() for every command line plugin we import
113 for plugin_name in self.config['plugins']:
115 plugin=__import__(plugin_name)
117 eval('plugin.'+plugin_name+'Commands._plug_init(self)')
118 except AttributeError:
125 #Everything sending an event should be here
126 def _measure_N_points(self, N, whatset=1):
128 general helper function for N-points measures
130 measure_points=self.list_of_events['measure_points']
131 wx.PostEvent(self.frame, measure_points(num_of_points=N, set=whatset))
134 points=self.frame.events_from_gui.get()
140 def _get_displayed_plot(self,dest=0):
142 returns the currently displayed plot.
144 wx.PostEvent(self.frame, self.list_of_events['get_displayed_plot'](dest=dest))
147 displayed_plot=self.events_from_gui.get()
152 return displayed_plot
154 def _send_plot(self,plots):
156 sends a plot to the GUI
158 wx.PostEvent(self.frame, self.list_of_events['plot_graph'](plots=plots))
161 def _find_plotmanip(self, name):
163 returns a plot manipulator function from its name
165 return self.plotmanip[self.config['plotmanips'].index(name)]
172 Sets a local configuration variable
174 Syntax: set [variable] [value]
176 def do_set(self,args):
177 #FIXME: some variables in self.config should be hidden or intelligently configurated...
180 print 'You must specify a variable and a value'
181 print 'Available variables:'
182 print self.config.keys()
184 if args[0] not in self.config.keys():
185 print 'This is not an internal Hooke variable!'
188 #FIXME:we should reload the config file and reset the config value
189 print self.config[args[0]]
192 try: #try to have a numeric value
194 except ValueError: #if it cannot be converted to float, it's None, or a string...
195 if value.lower()=='none':
200 self.config[key]=value
203 #PLAYLIST MANAGEMENT AND NAVIGATION
204 #------------------------------------
206 def help_loadlist(self):
209 Loads a file playlist
211 Syntax: loadlist [playlist file]
213 def do_loadlist(self, args):
214 #checking for args: if nothing is given as input, we warn and exit.
216 args=linp.alphainput('File to load?','',0,[])
219 play_to_load=arglist[0]
221 #We assume a Hooke playlist has the extension .hkp
222 if play_to_load[-4:] != '.hkp':
226 playxml=PlaylistXML()
227 self.current_list, self.playlist_generics=playxml.load(play_to_load)
228 self.current_playxml=playxml
230 print 'File not found.'
233 print 'Loaded %s curves' %len(self.current_list)
235 if 'pointer' in self.playlist_generics.keys():
236 self.pointer=int(self.playlist_generics['pointer'])
238 #if no pointer is found, set the current curve as the first curve of the loaded playlist
240 print 'Starting at curve ',self.pointer
242 self.current=self.current_list[self.pointer]
244 #resets saved/notes saved state
245 self.playlist_saved=0
246 self.playlist_name=''
252 def help_genlist(self):
255 Generates a file playlist.
256 Note it doesn't *save* it: see savelist for this.
258 If [input files] is a directory, it will use all files in the directory for playlist.
264 are all equivalent syntax.
266 Syntax: genlist [input files]
269 def do_genlist(self,args):
270 #args list is: input path, output name
272 args=linp.alphainput('Input files?','',1,[])
277 #if it's a directory, is like /directory/*.*
278 #FIXME: probably a bit kludgy.
279 if os.path.isdir(list_path):
280 if platform.system == 'Windows':
284 if list_path[-1] == SLASH:
285 list_path=list_path+'*.*'
287 list_path=list_path+SLASH+'*.*'
289 #expanding correctly the input list with the glob module :)
290 list_files=glob.glob(list_path)
294 for item in list_files:
296 self.current_list.append(lhc.HookeCurve(os.path.abspath(item)))
301 if len(self.current_list)>0:
302 self.current=self.current_list[self.pointer]
307 #resets saved/notes saved state
308 self.playlist_saved=0
309 self.playlist_name=''
315 def do_savelist(self,args):
318 Saves the current file playlist on disk.
320 Syntax: savelist [filename]
323 args=linp.alphainput('Output files?','',1,[])
327 self.playlist_generics['pointer']=self.pointer
329 #autocomplete filename if not specified
330 if output_filename[-4:] != '.hkp':
331 output_filename+='.hkp'
333 playxml=PlaylistXML()
334 playxml.export(self.current_list, self.playlist_generics)
335 playxml.save(output_filename)
337 #remembers we have saved playlist
338 self.playlist_saved=1
340 def help_addtolist(self):
343 Adds a file to the current playlist
345 Syntax: addtolist [filename]
347 def do_addtolist(self,args):
348 #args list is: input path
350 print 'You must give the input filename you want to add'
351 self.help_addtolist()
354 filenames=glob.glob(args)
356 for filename in filenames:
357 self.current_list.append(lhc.HookeCurve(os.path.abspath(filename)))
358 #we need to save playlist
359 self.playlist_saved=0
361 def help_printlist(self):
364 Prints the list of curves in the current playlist
368 def do_printlist(self,args):
369 for item in self.current_list:
376 Jumps to a given curve.
378 Syntax: jump {$curve}
380 If the curve is not in the current playlist, it politely asks if we want to add it.
382 def do_jump(self,filename):
384 jumps to the curve with the given filename.
385 if the filename is not in the playlist, it asks if we must add it or not.
389 filename=linp.alphainput('Jump to?','',0,[])
391 filepath=os.path.abspath(filename)
396 while item_not_found:
399 if self.current_list[c].path == filepath:
401 self.current=self.current_list[self.pointer]
407 #We've found the end of the list.
408 answer=linp.alphainput('Curve not found in playlist. Add it to list?','y',0,[])
409 if answer.lower()[0]=='y':
411 self.do_addtolist(filepath)
413 print 'Curve file not found.'
415 self.current=self.current_list[-1]
416 self.pointer=(len(current_list)-1)
422 def do_index(self,args):
425 Prints the index of the current curve in the list
429 print self.pointer+1, 'of', len(self.current_list)
435 Go the next curve in the playlist.
436 If we are at the last curve, we come back to the first.
440 def do_next(self,args):
441 self.current.curve.close_all()
442 if self.pointer == (len(self.current_list)-1):
444 print 'Playlist finished; back to first curve.'
448 self.current=self.current_list[self.pointer]
457 def help_previous(self,args):
460 Go to the previous curve in the playlist.
461 If we are at the first curve, we jump to the last.
465 def do_previous(self,args):
466 self.current.curve.close_all()
467 if self.pointer == 0:
468 self.pointer=(len(self.current_list)-1)
469 print 'Start of playlist; jump to last curve.'
473 self.current=self.current_list[self.pointer]
480 self.do_previous(args)
483 #PLOT INTERACTION COMMANDS
484 #-------------------------------
488 Plots the current force curve
492 def do_plot(self,args):
494 self.current.identify(self.drivers)
495 self.plots=self.current.curve.default_plots()
497 self.plots=self.current.curve.default_plots()
499 print 'Unexpected error occurred in do_plot().'
503 #apply the plotmanip functions eventually present
504 nplots=len(self.plots)
507 for function in self.plotmanip: #FIXME: something strange happens about self.plotmanip[0]
508 self.plots[c]=function(self.plots[c], self.current)
510 self.plots[c].xaxes=self.config['xaxes'] #FIXME: in the future, xaxes and yaxes should be set per-plot
511 self.plots[c].yaxes=self.config['yaxes']
515 self._send_plot(self.plots)
517 def _delta(self, set=1):
519 calculates the difference between two clicked points
521 print 'Click two points'
522 points=self._measure_N_points(N=2, whatset=set)
523 dx=abs(points[0].graph_coords[0]-points[1].graph_coords[0])
524 dy=abs(points[0].graph_coords[1]-points[1].graph_coords[1])
525 unitx=self.plots[points[0].dest].units[0]
526 unity=self.plots[points[0].dest].units[1]
527 return dx,unitx,dy,unity
529 def do_delta(self,args):
533 Measures the delta X and delta Y between two points.
537 dx,unitx,dy,unity=self._delta()
538 print str(dx)+' '+unitx
539 print str(dy)+' '+unity
541 def _point(self, set=1):
542 '''calculates the coordinates of a single clicked point'''
544 print 'Click one point'
545 point=self._measure_N_points(N=1, whatset=set)
547 x=point[0].graph_coords[0]
548 y=point[0].graph_coords[1]
549 unitx=self.plots[point[0].dest].units[0]
550 unity=self.plots[point[0].dest].units[1]
551 return x,unitx,y,unity
553 def do_point(self,args):
557 Returns the coordinates of a point on the graph.
561 x,unitx,y,unity=self._point()
562 print str(x)+' '+unitx
563 print str(y)+' '+unity
564 to_dump='point '+self.current.path+' '+str(x)+' '+unitx+', '+str(y)+' '+unity
565 self.outlet.push(to_dump)
568 def do_close(self,args=None):
571 Closes one of the two plots. If no arguments are given, the bottom plot is closed.
573 Syntax: close [top,bottom]
582 close_plot=self.list_of_events['close_plot']
583 wx.PostEvent(self.frame, close_plot(to_close=to_close))
585 def do_show(self,args=None):
590 show_plots=self.list_of_events['show_plots']
591 wx.PostEvent(self.frame, show_plots())
595 #PLOT EXPORT AND MANIPULATION COMMANDS
596 def help_export(self):
599 Saves the current plot as an image file
601 Syntax: export [filename] {plot to export}
603 The supported formats are PNG and EPS; the file extension of the filename is automatically recognized
604 and correctly exported. Resolution is (for now) fixed at 150 dpi.
606 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)
608 def do_export(self,args):
609 #FIXME: the bottom plot doesn't have the title
613 name=linp.alphainput('Filename?',self.current.path+'.png',0,[])
620 export_image=self.list_of_events['export_image']
621 wx.PostEvent(self.frame, export_image(name=name, dest=dest))
627 Saves the current curve as a text file
628 Columns are, in order:
629 X1 , Y1 , X2 , Y2 , X3 , Y3 ...
632 Syntax: txt [filename] {plot to export}
634 def do_txt(self,args):
636 def transposed2(lists, defval=0):
638 transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
640 (by Zoran Isailovski on the Python Cookbook online)
642 if not lists: return []
643 return map(lambda *row: [elem or defval for elem in row], *lists)
648 filename=linp.alphainput('Filename?',self.current.path+'.txt',0,[])
650 filename=linp.checkalphainput(args[0],self.current.path+'.txt',[])
652 whichplot=int(args[1])
657 for dataset in self.plots[whichplot].vectors:
658 for i in range(0,len(dataset)):
660 for value in dataset[i]:
661 columns[-1].append(str(value))
663 rows=transposed2(columns, 'nan')
664 rows=[' , '.join(item) for item in rows]
667 txtfile=open(filename,'w+')
672 #LOGGING, REPORTING, NOTETAKING
676 Writes or displays a note about the current curve.
677 If [anything] is empty, it displays the note, otherwise it adds a note.
678 The note is then saved in the playlist if you issue a savelist command
680 Syntax: note [anything]
682 def do_note(self,args):
684 print self.current_list[self.pointer].notes
686 #bypass UnicodeDecodeError troubles
688 args=args.decode('ascii')
690 args=args.decode('ascii','ignore')
694 self.current_list[self.pointer].notes=args
697 def help_notelog(self):
700 Writes a log of the notes taken during the session for the current
703 Syntax notelog [filename]
705 def do_notelog(self,args):
708 args=linp.alphainput('Notelog filename?','notelog.txt',0,[])
710 note_lines='Notes taken at '+time.asctime()+'\n'
711 for item in self.current_list:
712 if len(item.notes)>0:
713 #FIXME: log should be justified
714 #FIXME: file path should be truncated...
715 note_string=(item.path+' | '+item.notes+'\n')
716 note_lines+=note_string
722 except IOError, (ErrorNumber, ErrorMessage):
723 print 'Error: notes cannot be saved. Catched exception:'
728 def help_copylog(self):
731 Moves the annotated curves to another directory
733 Syntax copylog [directory]
735 def do_copylog(self,args):
738 args=linp.alphainput('Destination directory?','',0,[]) #TODO default
740 mydir=os.path.abspath(args)
741 if not os.path.isdir(mydir):
742 print 'Destination is not a directory.'
745 for item in self.current_list:
746 if len(item.notes)>0:
748 shutil.copy(item.path, mydir)
750 print 'OSError. Cannot copy file. Perhaps you gave me a wrong directory?'
755 def do_outlet_show(self,args):
758 Shows current content of outlet with index for reference
760 self.outlet.printbuf()
762 def do_outlet_undo(self, args):
765 Eliminates last entry in outlet
767 print 'Erasing last entry'
770 def do_outlet_delete(self, args):
772 Eliminates a particular entry from outlet
773 Syntax: outlet_delete n
776 print 'Index needed!, use outlet_show to know it'
778 self.outlet.delete(args)
784 #OS INTERACTION COMMANDS
789 Lists the files in the directory
794 def do_dir(self,args):
798 print glob.glob(args)
802 def do_ls(self,args):
808 Gives the current working directory.
812 def do_pwd(self,args):
818 Changes the current working directory
822 def do_cd(self,args):
823 mypath=os.path.abspath(args)
827 print 'I cannot access that directory.'
830 def help_system(self):
833 Executes a system command line and reports the output
835 Syntax system [command line]
838 def do_system(self,args):
839 waste=os.system(args)
841 def do_debug(self,args):
843 this is a dummy command where I put debugging things
845 print self.config['plotmanips']
848 def help_current(self):
851 Prints the current curve path.
855 def do_current(self,args):
856 print self.current.path
858 def do_version(self,args):
862 Prints the current version and codename, plus library version. Useful for debugging.
864 print 'Hooke '+__version__+' ('+__codename__+')'
865 print 'Released on: '+__releasedate__
867 print 'Python version: '+python_version
868 print 'WxPython version: '+wx_version
869 print 'wxMPL version: '+wxmpl_version
870 print 'Matplotlib version: '+mpl_version
871 print 'SciPy version: '+scipy_version
872 print 'NumPy version: '+numpy_version
874 print 'Platform: '+str(platform.uname())
876 print 'Loaded plugins:',self.config['loaded_plugins']
881 Exits the program cleanly.
886 def do_exit(self,args):
889 if (not self.playlist_saved) or (not self.notes_saved):
890 we_exit=linp.alphainput('You did not save your playlist and/or notes. Exit?','n',0,[])
892 we_exit=linp.alphainput('Exit?','y',0,[])
894 if we_exit[0].upper()=='Y':
895 wx.CallAfter(self.frame.Close)
902 def do_quit(self,args):
909 if __name__ == '__main__':