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)]
175 Sets a local configuration variable
177 Syntax: set [variable] [value]
179 def do_set(self,args):
180 #FIXME: some variables in self.config should be hidden or intelligently configurated...
183 print 'You must specify a variable and a value'
184 print 'Available variables:'
185 print self.config.keys()
187 if args[0] not in self.config.keys():
188 print 'This is not an internal Hooke variable!'
191 #FIXME:we should reload the config file and reset the config value
192 print self.config[args[0]]
195 try: #try to have a numeric value
197 except ValueError: #if it cannot be converted to float, it's None, or a string...
198 if value.lower()=='none':
203 self.config[key]=value
206 #PLAYLIST MANAGEMENT AND NAVIGATION
207 #------------------------------------
209 def help_loadlist(self):
212 Loads a file playlist
214 Syntax: loadlist [playlist file]
216 def do_loadlist(self, args):
217 #checking for args: if nothing is given as input, we warn and exit.
219 args=linp.safeinput('File to load?')
222 play_to_load=arglist[0]
224 #We assume a Hooke playlist has the extension .hkp
225 if play_to_load[-4:] != '.hkp':
229 playxml=PlaylistXML()
230 self.current_list, self.playlist_generics=playxml.load(play_to_load)
231 self.current_playxml=playxml
233 print 'File not found.'
236 print 'Loaded %s curves' %len(self.current_list)
238 if 'pointer' in self.playlist_generics.keys():
239 self.pointer=int(self.playlist_generics['pointer'])
241 #if no pointer is found, set the current curve as the first curve of the loaded playlist
243 print 'Starting at curve ',self.pointer
245 self.current=self.current_list[self.pointer]
247 #resets saved/notes saved state
248 self.playlist_saved=0
249 self.playlist_name=''
255 def help_genlist(self):
258 Generates a file playlist.
259 Note it doesn't *save* it: see savelist for this.
261 If [input files] is a directory, it will use all files in the directory for playlist.
267 are all equivalent syntax.
269 Syntax: genlist [input files]
272 def do_genlist(self,args):
273 #args list is: input path, output name
275 args=linp.safeinput('Input files?')
280 #if it's a directory, is like /directory/*.*
281 #FIXME: probably a bit kludgy.
282 if os.path.isdir(list_path):
283 if platform.system == 'Windows':
287 if list_path[-1] == SLASH:
288 list_path=list_path+'*.*'
290 list_path=list_path+SLASH+'*.*'
292 #expanding correctly the input list with the glob module :)
293 list_files=glob.glob(list_path)
297 for item in list_files:
299 self.current_list.append(lhc.HookeCurve(os.path.abspath(item)))
304 if len(self.current_list)>0:
305 self.current=self.current_list[self.pointer]
310 #resets saved/notes saved state
311 self.playlist_saved=0
312 self.playlist_name=''
318 def do_savelist(self,args):
321 Saves the current file playlist on disk.
323 Syntax: savelist [filename]
326 args=linp.safeinput('Output file?',['savedlist.txt'])
330 self.playlist_generics['pointer']=self.pointer
332 #autocomplete filename if not specified
333 if output_filename[-4:] != '.hkp':
334 output_filename+='.hkp'
336 playxml=PlaylistXML()
337 playxml.export(self.current_list, self.playlist_generics)
338 playxml.save(output_filename)
340 #remembers we have saved playlist
341 self.playlist_saved=1
343 def help_addtolist(self):
346 Adds a file to the current playlist
348 Syntax: addtolist [filename]
350 def do_addtolist(self,args):
351 #args list is: input path
353 print 'You must give the input filename you want to add'
354 self.help_addtolist()
357 filenames=glob.glob(args)
359 for filename in filenames:
360 self.current_list.append(lhc.HookeCurve(os.path.abspath(filename)))
361 #we need to save playlist
362 self.playlist_saved=0
364 def help_printlist(self):
367 Prints the list of curves in the current playlist
371 def do_printlist(self,args):
372 for item in self.current_list:
379 Jumps to a given curve.
381 Syntax: jump {$curve}
383 If the curve is not in the current playlist, it politely asks if we want to add it.
385 def do_jump(self,filename):
387 jumps to the curve with the given filename.
388 if the filename is not in the playlist, it asks if we must add it or not.
392 filename=linp.safeinput('Jump to?')
394 filepath=os.path.abspath(filename)
399 while item_not_found:
402 if self.current_list[c].path == filepath:
404 self.current=self.current_list[self.pointer]
410 #We've found the end of the list.
411 answer=linp.safeinput('Curve not found in playlist. Add it to list?',['y'])
412 if answer.lower()[0]=='y':
414 self.do_addtolist(filepath)
416 print 'Curve file not found.'
418 self.current=self.current_list[-1]
419 self.pointer=(len(current_list)-1)
425 def do_index(self,args):
428 Prints the index of the current curve in the list
432 print self.pointer+1, 'of', len(self.current_list)
438 Go the next curve in the playlist.
439 If we are at the last curve, we come back to the first.
443 def do_next(self,args):
444 self.current.curve.close_all()
445 if self.pointer == (len(self.current_list)-1):
447 print 'Playlist finished; back to first curve.'
451 self.current=self.current_list[self.pointer]
460 def help_previous(self,args):
463 Go to the previous curve in the playlist.
464 If we are at the first curve, we jump to the last.
468 def do_previous(self,args):
469 self.current.curve.close_all()
470 if self.pointer == 0:
471 self.pointer=(len(self.current_list)-1)
472 print 'Start of playlist; jump to last curve.'
476 self.current=self.current_list[self.pointer]
483 self.do_previous(args)
486 #PLOT INTERACTION COMMANDS
487 #-------------------------------
491 Plots the current force curve
495 def do_plot(self,args):
497 self.current.identify(self.drivers)
498 self.plots=self.current.curve.default_plots()
500 self.plots=self.current.curve.default_plots()
502 print 'Unexpected error occurred in do_plot().'
506 #apply the plotmanip functions eventually present
507 nplots=len(self.plots)
510 for function in self.plotmanip: #FIXME: something strange happens about self.plotmanip[0]
511 self.plots[c]=function(self.plots[c], self.current)
513 self.plots[c].xaxes=self.config['xaxes'] #FIXME: in the future, xaxes and yaxes should be set per-plot
514 self.plots[c].yaxes=self.config['yaxes']
518 self._send_plot(self.plots)
520 def _delta(self, set=1):
522 calculates the difference between two clicked points
524 print 'Click two points'
525 points=self._measure_N_points(N=2, whatset=set)
526 dx=abs(points[0].graph_coords[0]-points[1].graph_coords[0])
527 dy=abs(points[0].graph_coords[1]-points[1].graph_coords[1])
528 unitx=self.plots[points[0].dest].units[0]
529 unity=self.plots[points[0].dest].units[1]
530 return dx,unitx,dy,unity
532 def do_delta(self,args):
536 Measures the delta X and delta Y between two points.
540 dx,unitx,dy,unity=self._delta()
541 print str(dx)+' '+unitx
542 print str(dy)+' '+unity
544 def _point(self, set=1):
545 '''calculates the coordinates of a single clicked point'''
547 print 'Click one point'
548 point=self._measure_N_points(N=1, whatset=set)
550 x=point[0].graph_coords[0]
551 y=point[0].graph_coords[1]
552 unitx=self.plots[point[0].dest].units[0]
553 unity=self.plots[point[0].dest].units[1]
554 return x,unitx,y,unity
556 def do_point(self,args):
560 Returns the coordinates of a point on the graph.
564 x,unitx,y,unity=self._point()
565 print str(x)+' '+unitx
566 print str(y)+' '+unity
567 to_dump='point '+self.current.path+' '+str(x)+' '+unitx+', '+str(y)+' '+unity
568 self.outlet.push(to_dump)
571 def do_close(self,args=None):
574 Closes one of the two plots. If no arguments are given, the bottom plot is closed.
576 Syntax: close [top,bottom]
585 close_plot=self.list_of_events['close_plot']
586 wx.PostEvent(self.frame, close_plot(to_close=to_close))
588 def do_show(self,args=None):
593 show_plots=self.list_of_events['show_plots']
594 wx.PostEvent(self.frame, show_plots())
598 #PLOT EXPORT AND MANIPULATION COMMANDS
599 def help_export(self):
602 Saves the current plot as an image file
604 Syntax: export [filename] {plot to export}
606 The supported formats are PNG and EPS; the file extension of the filename is automatically recognized
607 and correctly exported. Resolution is (for now) fixed at 150 dpi.
609 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)
611 def do_export(self,args):
612 #FIXME: the bottom plot doesn't have the title
616 name=linp.safeinput('Filename?',[self.current.path+'.png'])
623 export_image=self.list_of_events['export_image']
624 wx.PostEvent(self.frame, export_image(name=name, dest=dest))
630 Saves the current curve as a text file
631 Columns are, in order:
632 X1 , Y1 , X2 , Y2 , X3 , Y3 ...
635 Syntax: txt [filename] {plot to export}
637 def do_txt(self,args):
639 def transposed2(lists, defval=0):
641 transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
643 (by Zoran Isailovski on the Python Cookbook online)
645 if not lists: return []
646 return map(lambda *row: [elem or defval for elem in row], *lists)
651 filename=linp.safeinput('Filename?',[self.current.path+'.txt'])
653 filename=linp.checkalphainput(args[0],self.current.path+'.txt',[])
655 whichplot=int(args[1])
660 for dataset in self.plots[whichplot].vectors:
661 for i in range(0,len(dataset)):
663 for value in dataset[i]:
664 columns[-1].append(str(value))
666 rows=transposed2(columns, 'nan')
667 rows=[' , '.join(item) for item in rows]
670 txtfile=open(filename,'w+')
675 #LOGGING, REPORTING, NOTETAKING
678 def do_note_old(self,args):
681 **deprecated**: Use note instead. Will be removed in 0.9
683 Writes or displays a note about the current curve.
684 If [anything] is empty, it displays the note, otherwise it adds a note.
685 The note is then saved in the playlist if you issue a savelist command
687 Syntax: note_old [anything]
691 print self.current_list[self.pointer].notes
693 #bypass UnicodeDecodeError troubles
695 args=args.decode('ascii')
697 args=args.decode('ascii','ignore')
701 self.current_list[self.pointer].notes=args
705 def do_note(self,args):
709 Writes or displays a note about the current curve.
710 If [anything] is empty, it displays the note, otherwise it adds a note.
711 The note is then saved in the playlist if you issue a savelist command.
713 Syntax: note_old [anything]
717 print self.current_list[self.pointer].notes
719 if self.notes_filename == None:
720 self.notes_filename=raw_input('Filename? ')
721 title_line='Notes taken at '+time.asctime()+'\n'
722 f=open(self.notes_filename,'w')
726 #bypass UnicodeDecodeError troubles
728 args=args.decode('ascii')
730 args=args.decode('ascii','ignore')
733 self.current_list[self.pointer].notes=args
735 f=open(self.notes_filename,'a+')
736 note_string=(self.current.path+' | '+self.current.notes+'\n')
740 def help_notelog(self):
743 Writes a log of the notes taken during the session for the current
746 Syntax notelog [filename]
748 def do_notelog(self,args):
751 args=linp.safeinput('Notelog filename?',['notelog.txt'])
753 note_lines='Notes taken at '+time.asctime()+'\n'
754 for item in self.current_list:
755 if len(item.notes)>0:
756 #FIXME: log should be justified
757 #FIXME: file path should be truncated...
758 note_string=(item.path+' | '+item.notes+'\n')
759 note_lines+=note_string
765 except IOError, (ErrorNumber, ErrorMessage):
766 print 'Error: notes cannot be saved. Catched exception:'
771 def help_copylog(self):
774 Moves the annotated curves to another directory
776 Syntax copylog [directory]
778 def do_copylog(self,args):
781 args=linp.safeinput('Destination directory?') #TODO default
783 mydir=os.path.abspath(args)
784 if not os.path.isdir(mydir):
785 print 'Destination is not a directory.'
788 for item in self.current_list:
789 if len(item.notes)>0:
791 shutil.copy(item.path, mydir)
792 except (OSError, IOError):
793 print 'Cannot copy file. '+item.path+' Perhaps you gave me a wrong directory?'
797 def do_outlet_show(self,args):
800 Shows current content of outlet with index for reference
802 self.outlet.printbuf()
804 def do_outlet_undo(self, args):
807 Eliminates last entry in outlet
809 print 'Erasing last entry'
812 def do_outlet_delete(self, args):
814 Eliminates a particular entry from outlet
815 Syntax: outlet_delete n
818 print 'Index needed!, use outlet_show to know it'
820 self.outlet.delete(args)
822 #OS INTERACTION COMMANDS
827 Lists the files in the directory
832 def do_dir(self,args):
836 print glob.glob(args)
840 def do_ls(self,args):
846 Gives the current working directory.
850 def do_pwd(self,args):
856 Changes the current working directory
860 def do_cd(self,args):
861 mypath=os.path.abspath(args)
865 print 'I cannot access that directory.'
868 def help_system(self):
871 Executes a system command line and reports the output
873 Syntax system [command line]
876 def do_system(self,args):
877 waste=os.system(args)
879 def do_debug(self,args):
881 this is a dummy command where I put debugging things
883 print self.config['plotmanips']
886 def help_current(self):
889 Prints the current curve path.
893 def do_current(self,args):
894 print self.current.path
896 def do_info(self,args):
900 Returns informations about the current curve.
902 print 'Path: ',self.current.path
903 print 'Experiment: ',self.current.curve.experiment
904 print 'Filetype: ',self.current.curve.filetype
905 for plot in self.current.curve.default_plots():
906 for set in plot.vectors:
907 lengths=[len(item) for item in set]
908 print 'Data set size: ',lengths
910 def do_version(self,args):
914 Prints the current version and codename, plus library version. Useful for debugging.
916 print 'Hooke '+__version__+' ('+__codename__+')'
917 print 'Released on: '+__releasedate__
919 print 'Python version: '+python_version
920 print 'WxPython version: '+wx_version
921 print 'wxMPL version: '+wxmpl_version
922 print 'Matplotlib version: '+mpl_version
923 print 'SciPy version: '+scipy_version
924 print 'NumPy version: '+numpy_version
926 print 'Platform: '+str(platform.uname())
928 print 'Loaded plugins:',self.config['loaded_plugins']
933 Exits the program cleanly.
938 def do_exit(self,args):
941 if (not self.playlist_saved) or (not self.notes_saved):
942 we_exit=linp.safeinput('You did not save your playlist and/or notes. Exit?',['n'])
944 we_exit=linp.safeinput('Exit?',['y'])
946 if we_exit[0].upper()=='Y':
947 wx.CallAfter(self.frame.Close)
954 def do_quit(self,args):
961 if __name__ == '__main__':