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=''
105 self.notes_filename=None
108 self.outlet=lout.Outlet()
110 #Data that must be saved in the playlist, related to the whole playlist (not individual curves)
111 self.playlist_generics={}
113 #make sure we execute _plug_init() for every command line plugin we import
114 for plugin_name in self.config['plugins']:
116 plugin=__import__(plugin_name)
118 eval('plugin.'+plugin_name+'Commands._plug_init(self)')
119 except AttributeError:
126 #Everything sending an event should be here
127 def _measure_N_points(self, N, whatset=1):
129 general helper function for N-points measures
131 measure_points=self.list_of_events['measure_points']
132 wx.PostEvent(self.frame, measure_points(num_of_points=N, set=whatset))
135 points=self.frame.events_from_gui.get()
141 def _get_displayed_plot(self,dest=0):
143 returns the currently displayed plot.
145 wx.PostEvent(self.frame, self.list_of_events['get_displayed_plot'](dest=dest))
148 displayed_plot=self.events_from_gui.get()
153 return displayed_plot
155 def _send_plot(self,plots):
157 sends a plot to the GUI
159 wx.PostEvent(self.frame, self.list_of_events['plot_graph'](plots=plots))
162 def _find_plotmanip(self, name):
164 returns a plot manipulator function from its name
166 return self.plotmanip[self.config['plotmanips'].index(name)]
173 Sets a local configuration variable
175 Syntax: set [variable] [value]
177 def do_set(self,args):
178 #FIXME: some variables in self.config should be hidden or intelligently configurated...
181 print 'You must specify a variable and a value'
182 print 'Available variables:'
183 print self.config.keys()
185 if args[0] not in self.config.keys():
186 print 'This is not an internal Hooke variable!'
189 #FIXME:we should reload the config file and reset the config value
190 print self.config[args[0]]
193 try: #try to have a numeric value
195 except ValueError: #if it cannot be converted to float, it's None, or a string...
196 if value.lower()=='none':
201 self.config[key]=value
204 #PLAYLIST MANAGEMENT AND NAVIGATION
205 #------------------------------------
207 def help_loadlist(self):
210 Loads a file playlist
212 Syntax: loadlist [playlist file]
214 def do_loadlist(self, args):
215 #checking for args: if nothing is given as input, we warn and exit.
217 args=linp.alphainput('File to load?','',0,[])
220 play_to_load=arglist[0]
222 #We assume a Hooke playlist has the extension .hkp
223 if play_to_load[-4:] != '.hkp':
227 playxml=PlaylistXML()
228 self.current_list, self.playlist_generics=playxml.load(play_to_load)
229 self.current_playxml=playxml
231 print 'File not found.'
234 print 'Loaded %s curves' %len(self.current_list)
236 if 'pointer' in self.playlist_generics.keys():
237 self.pointer=int(self.playlist_generics['pointer'])
239 #if no pointer is found, set the current curve as the first curve of the loaded playlist
241 print 'Starting at curve ',self.pointer
243 self.current=self.current_list[self.pointer]
245 #resets saved/notes saved state
246 self.playlist_saved=0
247 self.playlist_name=''
253 def help_genlist(self):
256 Generates a file playlist.
257 Note it doesn't *save* it: see savelist for this.
259 If [input files] is a directory, it will use all files in the directory for playlist.
265 are all equivalent syntax.
267 Syntax: genlist [input files]
270 def do_genlist(self,args):
271 #args list is: input path, output name
273 args=linp.alphainput('Input files?','',1,[])
278 #if it's a directory, is like /directory/*.*
279 #FIXME: probably a bit kludgy.
280 if os.path.isdir(list_path):
281 if platform.system == 'Windows':
285 if list_path[-1] == SLASH:
286 list_path=list_path+'*.*'
288 list_path=list_path+SLASH+'*.*'
290 #expanding correctly the input list with the glob module :)
291 list_files=glob.glob(list_path)
295 for item in list_files:
297 self.current_list.append(lhc.HookeCurve(os.path.abspath(item)))
302 if len(self.current_list)>0:
303 self.current=self.current_list[self.pointer]
308 #resets saved/notes saved state
309 self.playlist_saved=0
310 self.playlist_name=''
316 def do_savelist(self,args):
319 Saves the current file playlist on disk.
321 Syntax: savelist [filename]
324 args=linp.alphainput('Output files?','',1,[])
328 self.playlist_generics['pointer']=self.pointer
330 #autocomplete filename if not specified
331 if output_filename[-4:] != '.hkp':
332 output_filename+='.hkp'
334 playxml=PlaylistXML()
335 playxml.export(self.current_list, self.playlist_generics)
336 playxml.save(output_filename)
338 #remembers we have saved playlist
339 self.playlist_saved=1
341 def help_addtolist(self):
344 Adds a file to the current playlist
346 Syntax: addtolist [filename]
348 def do_addtolist(self,args):
349 #args list is: input path
351 print 'You must give the input filename you want to add'
352 self.help_addtolist()
355 filenames=glob.glob(args)
357 for filename in filenames:
358 self.current_list.append(lhc.HookeCurve(os.path.abspath(filename)))
359 #we need to save playlist
360 self.playlist_saved=0
362 def help_printlist(self):
365 Prints the list of curves in the current playlist
369 def do_printlist(self,args):
370 for item in self.current_list:
377 Jumps to a given curve.
379 Syntax: jump {$curve}
381 If the curve is not in the current playlist, it politely asks if we want to add it.
383 def do_jump(self,filename):
385 jumps to the curve with the given filename.
386 if the filename is not in the playlist, it asks if we must add it or not.
390 filename=linp.alphainput('Jump to?','',0,[])
392 filepath=os.path.abspath(filename)
397 while item_not_found:
400 if self.current_list[c].path == filepath:
402 self.current=self.current_list[self.pointer]
408 #We've found the end of the list.
409 answer=linp.alphainput('Curve not found in playlist. Add it to list?','y',0,[])
410 if answer.lower()[0]=='y':
412 self.do_addtolist(filepath)
414 print 'Curve file not found.'
416 self.current=self.current_list[-1]
417 self.pointer=(len(current_list)-1)
423 def do_index(self,args):
426 Prints the index of the current curve in the list
430 print self.pointer+1, 'of', len(self.current_list)
436 Go the next curve in the playlist.
437 If we are at the last curve, we come back to the first.
441 def do_next(self,args):
442 self.current.curve.close_all()
443 if self.pointer == (len(self.current_list)-1):
445 print 'Playlist finished; back to first curve.'
449 self.current=self.current_list[self.pointer]
458 def help_previous(self,args):
461 Go to the previous curve in the playlist.
462 If we are at the first curve, we jump to the last.
466 def do_previous(self,args):
467 self.current.curve.close_all()
468 if self.pointer == 0:
469 self.pointer=(len(self.current_list)-1)
470 print 'Start of playlist; jump to last curve.'
474 self.current=self.current_list[self.pointer]
481 self.do_previous(args)
484 #PLOT INTERACTION COMMANDS
485 #-------------------------------
489 Plots the current force curve
493 def do_plot(self,args):
495 self.current.identify(self.drivers)
496 self.plots=self.current.curve.default_plots()
498 self.plots=self.current.curve.default_plots()
500 print 'Unexpected error occurred in do_plot().'
504 #apply the plotmanip functions eventually present
505 nplots=len(self.plots)
508 for function in self.plotmanip: #FIXME: something strange happens about self.plotmanip[0]
509 self.plots[c]=function(self.plots[c], self.current)
511 self.plots[c].xaxes=self.config['xaxes'] #FIXME: in the future, xaxes and yaxes should be set per-plot
512 self.plots[c].yaxes=self.config['yaxes']
516 self._send_plot(self.plots)
518 def _delta(self, set=1):
520 calculates the difference between two clicked points
522 print 'Click two points'
523 points=self._measure_N_points(N=2, whatset=set)
524 dx=abs(points[0].graph_coords[0]-points[1].graph_coords[0])
525 dy=abs(points[0].graph_coords[1]-points[1].graph_coords[1])
526 unitx=self.plots[points[0].dest].units[0]
527 unity=self.plots[points[0].dest].units[1]
528 return dx,unitx,dy,unity
530 def do_delta(self,args):
534 Measures the delta X and delta Y between two points.
538 dx,unitx,dy,unity=self._delta()
539 print str(dx)+' '+unitx
540 print str(dy)+' '+unity
542 def _point(self, set=1):
543 '''calculates the coordinates of a single clicked point'''
545 print 'Click one point'
546 point=self._measure_N_points(N=1, whatset=set)
548 x=point[0].graph_coords[0]
549 y=point[0].graph_coords[1]
550 unitx=self.plots[point[0].dest].units[0]
551 unity=self.plots[point[0].dest].units[1]
552 return x,unitx,y,unity
554 def do_point(self,args):
558 Returns the coordinates of a point on the graph.
562 x,unitx,y,unity=self._point()
563 print str(x)+' '+unitx
564 print str(y)+' '+unity
565 to_dump='point '+self.current.path+' '+str(x)+' '+unitx+', '+str(y)+' '+unity
566 self.outlet.push(to_dump)
569 def do_close(self,args=None):
572 Closes one of the two plots. If no arguments are given, the bottom plot is closed.
574 Syntax: close [top,bottom]
583 close_plot=self.list_of_events['close_plot']
584 wx.PostEvent(self.frame, close_plot(to_close=to_close))
586 def do_show(self,args=None):
591 show_plots=self.list_of_events['show_plots']
592 wx.PostEvent(self.frame, show_plots())
596 #PLOT EXPORT AND MANIPULATION COMMANDS
597 def help_export(self):
600 Saves the current plot as an image file
602 Syntax: export [filename] {plot to export}
604 The supported formats are PNG and EPS; the file extension of the filename is automatically recognized
605 and correctly exported. Resolution is (for now) fixed at 150 dpi.
607 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)
609 def do_export(self,args):
610 #FIXME: the bottom plot doesn't have the title
614 name=linp.alphainput('Filename?',self.current.path+'.png',0,[])
621 export_image=self.list_of_events['export_image']
622 wx.PostEvent(self.frame, export_image(name=name, dest=dest))
628 Saves the current curve as a text file
629 Columns are, in order:
630 X1 , Y1 , X2 , Y2 , X3 , Y3 ...
633 Syntax: txt [filename] {plot to export}
635 def do_txt(self,args):
637 def transposed2(lists, defval=0):
639 transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
641 (by Zoran Isailovski on the Python Cookbook online)
643 if not lists: return []
644 return map(lambda *row: [elem or defval for elem in row], *lists)
649 filename=linp.alphainput('Filename?',self.current.path+'.txt',0,[])
651 filename=linp.checkalphainput(args[0],self.current.path+'.txt',[])
653 whichplot=int(args[1])
658 for dataset in self.plots[whichplot].vectors:
659 for i in range(0,len(dataset)):
661 for value in dataset[i]:
662 columns[-1].append(str(value))
664 rows=transposed2(columns, 'nan')
665 rows=[' , '.join(item) for item in rows]
668 txtfile=open(filename,'w+')
673 #LOGGING, REPORTING, NOTETAKING
676 def do_note_old(self,args):
679 **deprecated**: Use note instead. Will be removed in 0.9
681 Writes or displays a note about the current curve.
682 If [anything] is empty, it displays the note, otherwise it adds a note.
683 The note is then saved in the playlist if you issue a savelist command
685 Syntax: note_old [anything]
689 print self.current_list[self.pointer].notes
691 #bypass UnicodeDecodeError troubles
693 args=args.decode('ascii')
695 args=args.decode('ascii','ignore')
699 self.current_list[self.pointer].notes=args
703 def do_note(self,args):
707 Writes or displays a note about the current curve.
708 If [anything] is empty, it displays the note, otherwise it adds a note.
709 The note is then saved in the playlist if you issue a savelist command.
711 Syntax: note_old [anything]
715 print self.current_list[self.pointer].notes
717 if self.notes_filename == None:
718 self.notes_filename=raw_input('Filename? ')
719 title_line='Notes taken at '+time.asctime()+'\n'
720 f=open(self.notes_filename,'w')
724 #bypass UnicodeDecodeError troubles
726 args=args.decode('ascii')
728 args=args.decode('ascii','ignore')
731 self.current_list[self.pointer].notes=args
733 f=open(self.notes_filename,'a+')
734 note_string=(self.current.path+' | '+self.current.notes+'\n')
738 def help_notelog(self):
741 Writes a log of the notes taken during the session for the current
744 Syntax notelog [filename]
746 def do_notelog(self,args):
749 args=linp.alphainput('Notelog filename?','notelog.txt',0,[])
751 note_lines='Notes taken at '+time.asctime()+'\n'
752 for item in self.current_list:
753 if len(item.notes)>0:
754 #FIXME: log should be justified
755 #FIXME: file path should be truncated...
756 note_string=(item.path+' | '+item.notes+'\n')
757 note_lines+=note_string
763 except IOError, (ErrorNumber, ErrorMessage):
764 print 'Error: notes cannot be saved. Catched exception:'
769 def help_copylog(self):
772 Moves the annotated curves to another directory
774 Syntax copylog [directory]
776 def do_copylog(self,args):
779 args=linp.alphainput('Destination directory?','',0,[]) #TODO default
781 mydir=os.path.abspath(args)
782 if not os.path.isdir(mydir):
783 print 'Destination is not a directory.'
786 for item in self.current_list:
787 if len(item.notes)>0:
789 shutil.copy(item.path, mydir)
791 print 'OSError. Cannot copy file. Perhaps you gave me a wrong directory?'
796 def do_outlet_show(self,args):
799 Shows current content of outlet with index for reference
801 self.outlet.printbuf()
803 def do_outlet_undo(self, args):
806 Eliminates last entry in outlet
808 print 'Erasing last entry'
811 def do_outlet_delete(self, args):
813 Eliminates a particular entry from outlet
814 Syntax: outlet_delete n
817 print 'Index needed!, use outlet_show to know it'
819 self.outlet.delete(args)
825 #OS INTERACTION COMMANDS
830 Lists the files in the directory
835 def do_dir(self,args):
839 print glob.glob(args)
843 def do_ls(self,args):
849 Gives the current working directory.
853 def do_pwd(self,args):
859 Changes the current working directory
863 def do_cd(self,args):
864 mypath=os.path.abspath(args)
868 print 'I cannot access that directory.'
871 def help_system(self):
874 Executes a system command line and reports the output
876 Syntax system [command line]
879 def do_system(self,args):
880 waste=os.system(args)
882 def do_debug(self,args):
884 this is a dummy command where I put debugging things
886 print self.config['plotmanips']
889 def help_current(self):
892 Prints the current curve path.
896 def do_current(self,args):
897 print self.current.path
899 def do_version(self,args):
903 Prints the current version and codename, plus library version. Useful for debugging.
905 print 'Hooke '+__version__+' ('+__codename__+')'
906 print 'Released on: '+__releasedate__
908 print 'Python version: '+python_version
909 print 'WxPython version: '+wx_version
910 print 'wxMPL version: '+wxmpl_version
911 print 'Matplotlib version: '+mpl_version
912 print 'SciPy version: '+scipy_version
913 print 'NumPy version: '+numpy_version
915 print 'Platform: '+str(platform.uname())
917 print 'Loaded plugins:',self.config['loaded_plugins']
922 Exits the program cleanly.
927 def do_exit(self,args):
930 if (not self.playlist_saved) or (not self.notes_saved):
931 we_exit=linp.alphainput('You did not save your playlist and/or notes. Exit?','n',0,[])
933 we_exit=linp.alphainput('Exit?','y',0,[])
935 if we_exit[0].upper()=='Y':
936 wx.CallAfter(self.frame.Close)
943 def do_quit(self,args):
950 if __name__ == '__main__':