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:
124 #load default list, if possible
125 self.do_loadlist(self.config['defaultlist'])
128 #Everything sending an event should be here
129 def _measure_N_points(self, N, whatset=1):
131 general helper function for N-points measures
133 measure_points=self.list_of_events['measure_points']
134 wx.PostEvent(self.frame, measure_points(num_of_points=N, set=whatset))
137 points=self.frame.events_from_gui.get()
143 def _get_displayed_plot(self,dest=0):
145 returns the currently displayed plot.
147 wx.PostEvent(self.frame, self.list_of_events['get_displayed_plot'](dest=dest))
150 displayed_plot=self.events_from_gui.get()
155 return displayed_plot
157 def _send_plot(self,plots):
159 sends a plot to the GUI
161 wx.PostEvent(self.frame, self.list_of_events['plot_graph'](plots=plots))
164 def _find_plotmanip(self, name):
166 returns a plot manipulator function from its name
168 return self.plotmanip[self.config['plotmanips'].index(name)]
170 def _clickize(self, xvector, yvector, index):
172 returns a ClickedPoint() object from an index and vectors of x, y coordinates
176 point.absolute_coords=xvector[index],yvector[index]
177 point.find_graph_coords(xvector,yvector)
185 Sets a local configuration variable
187 Syntax: set [variable] [value]
189 def do_set(self,args):
190 #FIXME: some variables in self.config should be hidden or intelligently configurated...
193 print 'You must specify a variable and a value'
194 print 'Available variables:'
195 print self.config.keys()
197 if args[0] not in self.config.keys():
198 print 'This is not an internal Hooke variable!'
201 #FIXME:we should reload the config file and reset the config value
202 print self.config[args[0]]
205 try: #try to have a numeric value
207 except ValueError: #if it cannot be converted to float, it's None, or a string...
208 if value.lower()=='none':
213 self.config[key]=value
216 #PLAYLIST MANAGEMENT AND NAVIGATION
217 #------------------------------------
219 def help_loadlist(self):
222 Loads a file playlist
224 Syntax: loadlist [playlist file]
226 def do_loadlist(self, args):
227 #checking for args: if nothing is given as input, we warn and exit.
229 args=linp.safeinput('File to load?')
232 play_to_load=arglist[0]
234 #We assume a Hooke playlist has the extension .hkp
235 if play_to_load[-4:] != '.hkp':
239 playxml=PlaylistXML()
240 self.current_list, self.playlist_generics=playxml.load(play_to_load)
241 self.current_playxml=playxml
243 print 'File not found.'
246 print 'Loaded %s curves' %len(self.current_list)
248 if 'pointer' in self.playlist_generics.keys():
249 self.pointer=int(self.playlist_generics['pointer'])
251 #if no pointer is found, set the current curve as the first curve of the loaded playlist
253 print 'Starting at curve ',self.pointer
255 self.current=self.current_list[self.pointer]
257 #resets saved/notes saved state
258 self.playlist_saved=0
259 self.playlist_name=''
265 def help_genlist(self):
268 Generates a file playlist.
269 Note it doesn't *save* it: see savelist for this.
271 If [input files] is a directory, it will use all files in the directory for playlist.
277 are all equivalent syntax.
279 Syntax: genlist [input files]
282 def do_genlist(self,args):
283 #args list is: input path, output name
285 args=linp.safeinput('Input files?')
290 #if it's a directory, is like /directory/*.*
291 #FIXME: probably a bit kludgy.
292 if os.path.isdir(list_path):
293 if platform.system == 'Windows':
297 if list_path[-1] == SLASH:
298 list_path=list_path+'*.*'
300 list_path=list_path+SLASH+'*.*'
302 #expanding correctly the input list with the glob module :)
303 list_files=glob.glob(list_path)
307 for item in list_files:
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):
454 self.current.curve.close_all()
455 if self.pointer == (len(self.current_list)-1):
457 print 'Playlist finished; back to first curve.'
461 self.current=self.current_list[self.pointer]
470 def help_previous(self,args):
473 Go to the previous curve in the playlist.
474 If we are at the first curve, we jump to the last.
478 def do_previous(self,args):
479 self.current.curve.close_all()
480 if self.pointer == 0:
481 self.pointer=(len(self.current_list)-1)
482 print 'Start of playlist; jump to last curve.'
486 self.current=self.current_list[self.pointer]
493 self.do_previous(args)
496 #PLOT INTERACTION COMMANDS
497 #-------------------------------
501 Plots the current force curve
505 def do_plot(self,args):
507 self.current.identify(self.drivers)
508 self.plots=self.current.curve.default_plots()
510 self.plots=self.current.curve.default_plots()
512 print 'Unexpected error occurred in do_plot().'
516 #apply the plotmanip functions eventually present
517 nplots=len(self.plots)
520 for function in self.plotmanip: #FIXME: something strange happens about self.plotmanip[0]
521 self.plots[c]=function(self.plots[c], self.current)
523 self.plots[c].xaxes=self.config['xaxes'] #FIXME: in the future, xaxes and yaxes should be set per-plot
524 self.plots[c].yaxes=self.config['yaxes']
528 self._send_plot(self.plots)
530 def _delta(self, set=1):
532 calculates the difference between two clicked points
534 print 'Click two points'
535 points=self._measure_N_points(N=2, whatset=set)
536 dx=abs(points[0].graph_coords[0]-points[1].graph_coords[0])
537 dy=abs(points[0].graph_coords[1]-points[1].graph_coords[1])
538 unitx=self.plots[points[0].dest].units[0]
539 unity=self.plots[points[0].dest].units[1]
540 return dx,unitx,dy,unity
542 def do_delta(self,args):
546 Measures the delta X and delta Y between two points.
550 dx,unitx,dy,unity=self._delta()
551 print str(dx)+' '+unitx
552 print str(dy)+' '+unity
554 def _point(self, set=1):
555 '''calculates the coordinates of a single clicked point'''
557 print 'Click one point'
558 point=self._measure_N_points(N=1, whatset=set)
560 x=point[0].graph_coords[0]
561 y=point[0].graph_coords[1]
562 unitx=self.plots[point[0].dest].units[0]
563 unity=self.plots[point[0].dest].units[1]
564 return x,unitx,y,unity
566 def do_point(self,args):
570 Returns the coordinates of a point on the graph.
574 x,unitx,y,unity=self._point()
575 print str(x)+' '+unitx
576 print str(y)+' '+unity
577 to_dump='point '+self.current.path+' '+str(x)+' '+unitx+', '+str(y)+' '+unity
578 self.outlet.push(to_dump)
581 def do_close(self,args=None):
584 Closes one of the two plots. If no arguments are given, the bottom plot is closed.
586 Syntax: close [top,bottom]
595 close_plot=self.list_of_events['close_plot']
596 wx.PostEvent(self.frame, close_plot(to_close=to_close))
598 def do_show(self,args=None):
603 show_plots=self.list_of_events['show_plots']
604 wx.PostEvent(self.frame, show_plots())
608 #PLOT EXPORT AND MANIPULATION COMMANDS
609 def help_export(self):
612 Saves the current plot as an image file
614 Syntax: export [filename] {plot to export}
616 The supported formats are PNG and EPS; the file extension of the filename is automatically recognized
617 and correctly exported. Resolution is (for now) fixed at 150 dpi.
619 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)
621 def do_export(self,args):
622 #FIXME: the bottom plot doesn't have the title
626 name=linp.safeinput('Filename?',[self.current.path+'.png'])
633 export_image=self.list_of_events['export_image']
634 wx.PostEvent(self.frame, export_image(name=name, dest=dest))
640 Saves the current curve as a text file
641 Columns are, in order:
642 X1 , Y1 , X2 , Y2 , X3 , Y3 ...
645 Syntax: txt [filename] {plot to export}
647 def do_txt(self,args):
649 def transposed2(lists, defval=0):
651 transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
653 (by Zoran Isailovski on the Python Cookbook online)
655 if not lists: return []
656 return map(lambda *row: [elem or defval for elem in row], *lists)
661 filename=linp.safeinput('Filename?',[self.current.path+'.txt'])
663 filename=linp.checkalphainput(args[0],self.current.path+'.txt',[])
665 whichplot=int(args[1])
670 for dataset in self.plots[whichplot].vectors:
671 for i in range(0,len(dataset)):
673 for value in dataset[i]:
674 columns[-1].append(str(value))
676 rows=transposed2(columns, 'nan')
677 rows=[' , '.join(item) for item in rows]
680 txtfile=open(filename,'w+')
685 #LOGGING, REPORTING, NOTETAKING
688 def do_note_old(self,args):
691 **deprecated**: Use note instead. Will be removed in 0.9
693 Writes or displays a note about the current curve.
694 If [anything] is empty, it displays the note, otherwise it adds a note.
695 The note is then saved in the playlist if you issue a savelist command
697 Syntax: note_old [anything]
701 print self.current_list[self.pointer].notes
703 #bypass UnicodeDecodeError troubles
705 args=args.decode('ascii')
707 args=args.decode('ascii','ignore')
711 self.current_list[self.pointer].notes=args
715 def do_note(self,args):
719 Writes or displays a note about the current curve.
720 If [anything] is empty, it displays the note, otherwise it adds a note.
721 The note is then saved in the playlist if you issue a savelist command.
723 Syntax: note_old [anything]
727 print self.current_list[self.pointer].notes
729 if self.notes_filename == None:
730 self.notes_filename=raw_input('Notebook filename? ')
731 title_line='Notes taken at '+time.asctime()+'\n'
732 f=open(self.notes_filename,'w')
736 #bypass UnicodeDecodeError troubles
738 args=args.decode('ascii')
740 args=args.decode('ascii','ignore')
743 self.current_list[self.pointer].notes=args
745 f=open(self.notes_filename,'a+')
746 note_string=(self.current.path+' | '+self.current.notes+'\n')
750 def help_notelog(self):
753 Writes a log of the notes taken during the session for the current
756 Syntax notelog [filename]
758 def do_notelog(self,args):
761 args=linp.safeinput('Notelog filename?',['notelog.txt'])
763 note_lines='Notes taken at '+time.asctime()+'\n'
764 for item in self.current_list:
765 if len(item.notes)>0:
766 #FIXME: log should be justified
767 #FIXME: file path should be truncated...
768 note_string=(item.path+' | '+item.notes+'\n')
769 note_lines+=note_string
775 except IOError, (ErrorNumber, ErrorMessage):
776 print 'Error: notes cannot be saved. Catched exception:'
781 def help_copylog(self):
784 Moves the annotated curves to another directory
786 Syntax copylog [directory]
788 def do_copylog(self,args):
791 args=linp.safeinput('Destination directory?') #TODO default
793 mydir=os.path.abspath(args)
794 if not os.path.isdir(mydir):
795 print 'Destination is not a directory.'
798 for item in self.current_list:
799 if len(item.notes)>0:
801 shutil.copy(item.path, mydir)
802 except (OSError, IOError):
803 print 'Cannot copy file. '+item.path+' Perhaps you gave me a wrong directory?'
807 def do_outlet_show(self,args):
810 Shows current content of outlet with index for reference
812 self.outlet.printbuf()
814 def do_outlet_undo(self, args):
817 Eliminates last entry in outlet
819 print 'Erasing last entry'
822 def do_outlet_delete(self, args):
824 Eliminates a particular entry from outlet
825 Syntax: outlet_delete n
828 print 'Index needed!, use outlet_show to know it'
830 self.outlet.delete(args)
832 #OS INTERACTION COMMANDS
837 Lists the files in the directory
842 def do_dir(self,args):
846 print glob.glob(args)
850 def do_ls(self,args):
856 Gives the current working directory.
860 def do_pwd(self,args):
866 Changes the current working directory
870 def do_cd(self,args):
871 mypath=os.path.abspath(args)
875 print 'I cannot access that directory.'
878 def help_system(self):
881 Executes a system command line and reports the output
883 Syntax system [command line]
886 def do_system(self,args):
887 waste=os.system(args)
889 def do_debug(self,args):
891 this is a dummy command where I put debugging things
893 print self.config['plotmanips']
896 def help_current(self):
899 Prints the current curve path.
903 def do_current(self,args):
904 print self.current.path
906 def do_info(self,args):
910 Returns informations about the current curve.
912 print 'Path: ',self.current.path
913 print 'Experiment: ',self.current.curve.experiment
914 print 'Filetype: ',self.current.curve.filetype
915 for plot in self.current.curve.default_plots():
916 for set in plot.vectors:
917 lengths=[len(item) for item in set]
918 print 'Data set size: ',lengths
920 def do_version(self,args):
924 Prints the current version and codename, plus library version. Useful for debugging.
926 print 'Hooke '+__version__+' ('+__codename__+')'
927 print 'Released on: '+__releasedate__
929 print 'Python version: '+python_version
930 print 'WxPython version: '+wx_version
931 print 'wxMPL version: '+wxmpl_version
932 print 'Matplotlib version: '+mpl_version
933 print 'SciPy version: '+scipy_version
934 print 'NumPy version: '+numpy_version
936 print 'Platform: '+str(platform.uname())
938 print 'Loaded plugins:',self.config['loaded_plugins']
943 Exits the program cleanly.
948 def do_exit(self,args):
951 if (not self.playlist_saved) or (not self.notes_saved):
952 we_exit=linp.safeinput('You did not save your playlist and/or notes. Exit?',['n'])
954 we_exit=linp.safeinput('Exit?',['y'])
956 if we_exit[0].upper()=='Y':
957 wx.CallAfter(self.frame.Close)
964 def do_quit(self,args):
971 if __name__ == '__main__':