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.
13 from libhooke import * #FIXME
14 import libhookecurve as lhc
16 import libinput as linp
17 import liboutlet as lout
19 from libhooke import WX_GOOD
20 from libhooke import HOOKE_VERSION
23 wxversion.select(WX_GOOD)
26 from wx.lib.newevent import NewEvent
27 from matplotlib.numerix import * #FIXME
29 import xml.dom.minidom
30 import sys, os, os.path, glob, shutil
37 global __releasedate__
38 __version__ = HOOKE_VERSION[0]
39 __codename__ = HOOKE_VERSION[1]
40 __releasedate__ = HOOKE_VERSION[2]
42 from matplotlib import __version__ as mpl_version
43 from wx import __version__ as wx_version
44 from wxmpl import __version__ as wxmpl_version
45 from scipy import __version__ as scipy_version
46 from numpy import __version__ as numpy_version
47 from sys import version as python_version
51 class HookeCli(cmd.Cmd):
53 def __init__(self,frame,list_of_events,events_from_gui,config,drivers):
54 cmd.Cmd.__init__(self)
56 self.prompt = 'hooke: '
59 self.current_list=[] #the playlist we're using
61 self.current=None #the current curve under analysis.
64 The actual hierarchy of the "current curve" is a bit complex:
66 self.current = the lhc.HookeCurve container object of the current curve
67 self.current.curve = the current "real" curve object as defined in the filetype driver class
68 self.current.curve.default_plots() = the default plots of the filetype driver.
70 The plot objects obtained by mean of self.current.curve.default_plots()
71 then undergoes modifications by the plotmanip
72 modifier functions. The modified plot is saved in self.plots and used if needed by other functions.
76 self.pointer=0 #a pointer to navigate the current list
78 #Things that come from outside
79 self.frame=frame #the wx frame we refer to
80 self.list_of_events=list_of_events #a list of wx events we use to interact with the GUI
81 self.events_from_gui=events_from_gui #the Queue object we use to have messages from the GUI
82 self.config=config #the configuration dictionary
83 self.drivers=drivers #the file format drivers
85 #get plot manipulation functions
86 plotmanip_functions=[]
87 for object_name in dir(self):
88 if object_name[0:9]=='plotmanip':
89 plotmanip_functions.append(getattr(self,object_name))
90 #put plotmanips in order
91 self.plotmanip=[None for item in self.config['plotmanips']]
92 for item in plotmanip_functions:
93 namefunction=item.__name__[10:]
94 if namefunction in self.config['plotmanips']:
95 nameindex=self.config['plotmanips'].index(namefunction) #index of function in plotmanips config
96 self.plotmanip[nameindex] = item
101 self.playlist_saved=0 #Did we save the playlist?
102 self.playlist_name='' #Name of playlist
103 self.notes_saved=1 #Did we save the notes?
104 self.notes_filename=None #Name of notes
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:
123 #load default list, if possible
124 self.do_loadlist(self.config['defaultlist'])
127 #Everything sending an event should be here
128 def _measure_N_points(self, N, whatset=1):
130 general helper function for N-points measures
132 wx.PostEvent(self.frame,self.list_of_events['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)]
168 def _clickize(self, xvector, yvector, index):
170 returns a ClickedPoint() object from an index and vectors of x, y coordinates
174 point.absolute_coords=xvector[index],yvector[index]
175 point.find_graph_coords(xvector,yvector)
183 Sets a local configuration variable
185 Syntax: set [variable] [value]
187 def do_set(self,args):
188 #FIXME: some variables in self.config should be hidden or intelligently configurated...
191 print 'You must specify a variable and a value'
192 print 'Available variables:'
193 print self.config.keys()
195 if args[0] not in self.config.keys():
196 print 'This is not an internal Hooke variable!'
199 #FIXME:we should reload the config file and reset the config value
200 print self.config[args[0]]
203 try: #try to have a numeric value
205 except ValueError: #if it cannot be converted to float, it's None, or a string...
207 if value.lower()=='none':
212 self.config[key]=value
215 #PLAYLIST MANAGEMENT AND NAVIGATION
216 #------------------------------------
218 def help_loadlist(self):
221 Loads a file playlist
223 Syntax: loadlist [playlist file]
225 def do_loadlist(self, args):
226 #checking for args: if nothing is given as input, we warn and exit.
228 args=linp.safeinput('File to load?')
231 play_to_load=arglist[0]
233 #We assume a Hooke playlist has the extension .hkp
234 if play_to_load[-4:] != '.hkp':
238 playxml=PlaylistXML()
239 self.current_list, self.playlist_generics=playxml.load(play_to_load)
240 self.current_playxml=playxml
242 print 'File not found.'
245 print 'Loaded %s curves' %len(self.current_list)
247 if 'pointer' in self.playlist_generics.keys():
248 self.pointer=int(self.playlist_generics['pointer'])
250 #if no pointer is found, set the current curve as the first curve of the loaded playlist
252 print 'Starting at curve ',self.pointer
254 self.current=self.current_list[self.pointer]
256 #resets saved/notes saved state
257 self.playlist_saved=0
258 self.playlist_name=''
264 def help_genlist(self):
267 Generates a file playlist.
268 Note it doesn't *save* it: see savelist for this.
270 If [input files] is a directory, it will use all files in the directory for playlist.
276 are all equivalent syntax.
278 Syntax: genlist [input files]
281 def do_genlist(self,args):
282 #args list is: input path, output name
284 args=linp.safeinput('Input files?')
289 #if it's a directory, is like /directory/*.*
290 #FIXME: probably a bit kludgy.
291 if os.path.isdir(list_path):
292 if platform.system == 'Windows':
296 if list_path[-1] == SLASH:
297 list_path=list_path+'*.*'
299 list_path=list_path+SLASH+'*.*'
301 #expanding correctly the input list with the glob module :)
302 list_files=glob.glob(list_path)
306 for item in list_files:
308 if os.path.isfile(item):
309 self.current_list.append(lhc.HookeCurve(os.path.abspath(item)))
314 if len(self.current_list)>0:
315 self.current=self.current_list[self.pointer]
320 #resets saved/notes saved state
321 self.playlist_saved=0
322 self.playlist_name=''
328 def do_savelist(self,args):
331 Saves the current file playlist on disk.
333 Syntax: savelist [filename]
336 args=linp.safeinput('Output file?',['savedlist.txt'])
340 self.playlist_generics['pointer']=self.pointer
342 #autocomplete filename if not specified
343 if output_filename[-4:] != '.hkp':
344 output_filename+='.hkp'
346 playxml=PlaylistXML()
347 playxml.export(self.current_list, self.playlist_generics)
348 playxml.save(output_filename)
350 #remembers we have saved playlist
351 self.playlist_saved=1
353 def help_addtolist(self):
356 Adds a file to the current playlist
358 Syntax: addtolist [filename]
360 def do_addtolist(self,args):
361 #args list is: input path
363 print 'You must give the input filename you want to add'
364 self.help_addtolist()
367 filenames=glob.glob(args)
369 for filename in filenames:
370 self.current_list.append(lhc.HookeCurve(os.path.abspath(filename)))
371 #we need to save playlist
372 self.playlist_saved=0
374 def help_printlist(self):
377 Prints the list of curves in the current playlist
381 def do_printlist(self,args):
382 for item in self.current_list:
389 Jumps to a given curve.
391 Syntax: jump {$curve}
393 If the curve is not in the current playlist, it politely asks if we want to add it.
395 def do_jump(self,filename):
397 jumps to the curve with the given filename.
398 if the filename is not in the playlist, it asks if we must add it or not.
402 filename=linp.safeinput('Jump to?')
404 filepath=os.path.abspath(filename)
409 while item_not_found:
412 if self.current_list[c].path == filepath:
414 self.current=self.current_list[self.pointer]
420 #We've found the end of the list.
421 answer=linp.safeinput('Curve not found in playlist. Add it to list?',['y'])
422 if answer.lower()[0]=='y':
424 self.do_addtolist(filepath)
426 print 'Curve file not found.'
428 self.current=self.current_list[-1]
429 self.pointer=(len(current_list)-1)
435 def do_index(self,args):
438 Prints the index of the current curve in the list
442 print self.pointer+1, 'of', len(self.current_list)
448 Go the next curve in the playlist.
449 If we are at the last curve, we come back to the first.
453 def do_next(self,args):
455 self.current.curve.close_all()
457 print 'No curve file loaded, currently!'
458 print 'This should not happen, report to http://code.google.com/p/hooke'
461 if self.pointer == (len(self.current_list)-1):
463 print 'Playlist finished; back to first curve.'
467 self.current=self.current_list[self.pointer]
476 def help_previous(self,args):
479 Go to the previous curve in the playlist.
480 If we are at the first curve, we jump to the last.
484 def do_previous(self,args):
486 self.current.curve.close_all()
488 print 'No curve file loaded, currently!'
489 print 'This should not happen, report to http://code.google.com/p/hooke'
491 if self.pointer == 0:
492 self.pointer=(len(self.current_list)-1)
493 print 'Start of playlist; jump to last curve.'
497 self.current=self.current_list[self.pointer]
504 self.do_previous(args)
507 #PLOT INTERACTION COMMANDS
508 #-------------------------------
512 Plots the current force curve
516 def do_plot(self,args):
518 self.current.identify(self.drivers)
519 self.plots=self.current.curve.default_plots()
521 self.plots=self.current.curve.default_plots()
523 print 'Unexpected error occurred in do_plot().'
527 #apply the plotmanip functions eventually present
528 nplots=len(self.plots)
531 for function in self.plotmanip: #FIXME: something strange happens about self.plotmanip[0]
532 self.plots[c]=function(self.plots[c], self.current)
534 self.plots[c].xaxes=self.config['xaxes'] #FIXME: in the future, xaxes and yaxes should be set per-plot
535 self.plots[c].yaxes=self.config['yaxes']
539 self._send_plot(self.plots)
541 def _delta(self, set=1):
543 calculates the difference between two clicked points
545 print 'Click two points'
546 points=self._measure_N_points(N=2, whatset=set)
547 dx=abs(points[0].graph_coords[0]-points[1].graph_coords[0])
548 dy=abs(points[0].graph_coords[1]-points[1].graph_coords[1])
549 unitx=self.plots[points[0].dest].units[0]
550 unity=self.plots[points[0].dest].units[1]
551 return dx,unitx,dy,unity
553 def do_delta(self,args):
557 Measures the delta X and delta Y between two points.
561 dx,unitx,dy,unity=self._delta()
562 print str(dx)+' '+unitx
563 print str(dy)+' '+unity
565 def _point(self, set=1):
566 '''calculates the coordinates of a single clicked point'''
568 print 'Click one point'
569 point=self._measure_N_points(N=1, whatset=set)
571 x=point[0].graph_coords[0]
572 y=point[0].graph_coords[1]
573 unitx=self.plots[point[0].dest].units[0]
574 unity=self.plots[point[0].dest].units[1]
575 return x,unitx,y,unity
577 def do_point(self,args):
581 Returns the coordinates of a point on the graph.
585 x,unitx,y,unity=self._point()
586 print str(x)+' '+unitx
587 print str(y)+' '+unity
588 to_dump='point '+self.current.path+' '+str(x)+' '+unitx+', '+str(y)+' '+unity
589 self.outlet.push(to_dump)
592 def do_close(self,args=None):
595 Closes one of the two plots. If no arguments are given, the bottom plot is closed.
597 Syntax: close [top,bottom]
606 close_plot=self.list_of_events['close_plot']
607 wx.PostEvent(self.frame, close_plot(to_close=to_close))
609 def do_show(self,args=None):
614 show_plots=self.list_of_events['show_plots']
615 wx.PostEvent(self.frame, show_plots())
619 #PLOT EXPORT AND MANIPULATION COMMANDS
620 def help_export(self):
623 Saves the current plot as an image file
625 Syntax: export [filename] {plot to export}
627 The supported formats are PNG and EPS; the file extension of the filename is automatically recognized
628 and correctly exported. Resolution is (for now) fixed at 150 dpi.
630 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)
632 def do_export(self,args):
633 #FIXME: the bottom plot doesn't have the title
638 #FIXME: We have to go into the libinput stuff and fix it, for now here's a dummy replacement...
639 #name=linp.safeinput('Filename?',[self.current.path+'.png'])
640 name=raw_input('Filename? ')
647 export_image=self.list_of_events['export_image']
648 wx.PostEvent(self.frame, export_image(name=name, dest=dest))
654 Saves the current curve as a text file
655 Columns are, in order:
656 X1 , Y1 , X2 , Y2 , X3 , Y3 ...
659 Syntax: txt [filename] {plot to export}
661 def do_txt(self,args):
663 def transposed2(lists, defval=0):
665 transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
667 (by Zoran Isailovski on the Python Cookbook online)
669 if not lists: return []
670 return map(lambda *row: [elem or defval for elem in row], *lists)
675 filename=linp.safeinput('Filename?',[self.current.path+'.txt'])
677 filename=linp.checkalphainput(args[0],self.current.path+'.txt',[])
679 whichplot=int(args[1])
684 for dataset in self.plots[whichplot].vectors:
685 for i in range(0,len(dataset)):
687 for value in dataset[i]:
688 columns[-1].append(str(value))
690 rows=transposed2(columns, 'nan')
691 rows=[' , '.join(item) for item in rows]
694 txtfile=open(filename,'w+')
695 #Save units of measure in header
696 txtfile.write('X:'+self.plots[whichplot].units[0]+'\n')
697 txtfile.write('Y:'+self.plots[whichplot].units[1]+'\n')
702 #LOGGING, REPORTING, NOTETAKING
705 def do_note_old(self,args):
708 **deprecated**: Use note instead. Will be removed in 0.9
710 Writes or displays a note about the current curve.
711 If [anything] is empty, it displays the note, otherwise it adds a note.
712 The note is then saved in the playlist if you issue a savelist command
714 Syntax: note_old [anything]
718 print self.current_list[self.pointer].notes
720 #bypass UnicodeDecodeError troubles
722 args=args.decode('ascii')
724 args=args.decode('ascii','ignore')
728 self.current_list[self.pointer].notes=args
732 def do_note(self,args):
736 Writes or displays a note about the current curve.
737 If [anything] is empty, it displays the note, otherwise it adds a note.
738 The note is then saved in the playlist if you issue a savelist command.
740 Syntax: note_old [anything]
744 print self.current_list[self.pointer].notes
746 if self.notes_filename == None:
747 self.notes_filename=raw_input('Notebook filename? ')
748 title_line='Notes taken at '+time.asctime()+'\n'
749 f=open(self.notes_filename,'w')
753 #bypass UnicodeDecodeError troubles
755 args=args.decode('ascii')
757 args=args.decode('ascii','ignore')
760 self.current_list[self.pointer].notes=args
762 f=open(self.notes_filename,'a+')
763 note_string=(self.current.path+' | '+self.current.notes+'\n')
767 def help_notelog(self):
770 Writes a log of the notes taken during the session for the current
773 Syntax notelog [filename]
775 def do_notelog(self,args):
778 args=linp.safeinput('Notelog filename?',['notelog.txt'])
780 note_lines='Notes taken at '+time.asctime()+'\n'
781 for item in self.current_list:
782 if len(item.notes)>0:
783 #FIXME: log should be justified
784 #FIXME: file path should be truncated...
785 note_string=(item.path+' | '+item.notes+'\n')
786 note_lines+=note_string
792 except IOError, (ErrorNumber, ErrorMessage):
793 print 'Error: notes cannot be saved. Catched exception:'
798 def help_copylog(self):
801 Moves the annotated curves to another directory
803 Syntax copylog [directory]
805 def do_copylog(self,args):
808 args=linp.safeinput('Destination directory?') #TODO default
810 mydir=os.path.abspath(args)
811 if not os.path.isdir(mydir):
812 print 'Destination is not a directory.'
815 for item in self.current_list:
816 if len(item.notes)>0:
818 shutil.copy(item.path, mydir)
819 except (OSError, IOError):
820 print 'Cannot copy file. '+item.path+' Perhaps you gave me a wrong directory?'
824 def do_outlet_show(self,args):
827 Shows current content of outlet with index for reference
829 self.outlet.printbuf()
831 def do_outlet_undo(self, args):
834 Eliminates last entry in outlet
836 print 'Erasing last entry'
839 def do_outlet_delete(self, args):
841 Eliminates a particular entry from outlet
842 Syntax: outlet_delete n
845 print 'Index needed!, use outlet_show to know it'
847 self.outlet.delete(args)
849 #OS INTERACTION COMMANDS
854 Lists the files in the directory
859 def do_dir(self,args):
863 print glob.glob(args)
867 def do_ls(self,args):
873 Gives the current working directory.
877 def do_pwd(self,args):
883 Changes the current working directory
887 def do_cd(self,args):
888 mypath=os.path.abspath(args)
892 print 'I cannot access that directory.'
895 def help_system(self):
898 Executes a system command line and reports the output
900 Syntax system [command line]
903 def do_system(self,args):
904 waste=os.system(args)
906 def do_debug(self,args):
908 this is a dummy command where I put debugging things
910 print self.config['plotmanips']
913 def help_current(self):
916 Prints the current curve path.
920 def do_current(self,args):
921 print self.current.path
923 def do_info(self,args):
927 Returns informations about the current curve.
929 print 'Path: ',self.current.path
930 print 'Experiment: ',self.current.curve.experiment
931 print 'Filetype: ',self.current.curve.filetype
932 for plot in self.current.curve.default_plots():
933 for set in plot.vectors:
934 lengths=[len(item) for item in set]
935 print 'Data set size: ',lengths
937 def do_version(self,args):
941 Prints the current version and codename, plus library version. Useful for debugging.
943 print 'Hooke '+__version__+' ('+__codename__+')'
944 print 'Released on: '+__releasedate__
946 print 'Python version: '+python_version
947 print 'WxPython version: '+wx_version
948 print 'wxMPL version: '+wxmpl_version
949 print 'Matplotlib version: '+mpl_version
950 print 'SciPy version: '+scipy_version
951 print 'NumPy version: '+numpy_version
953 print 'Platform: '+str(platform.uname())
955 print 'Loaded plugins:',self.config['loaded_plugins']
960 Exits the program cleanly.
965 def do_exit(self,args):
968 if (not self.playlist_saved) or (not self.notes_saved):
969 we_exit=linp.safeinput('You did not save your playlist and/or notes. Exit?',['n'])
971 we_exit=linp.safeinput('Exit?',['y'])
973 if we_exit[0].upper()=='Y':
974 wx.CallAfter(self.frame.Close)
981 def do_quit(self,args):
985 if __name__ == '__main__':