1 # Copyright (C) 2007-2010 Massimo Sandal <devicerandom@gmail.com>
2 # W. Trevor King <wking@drexel.edu>
4 # This file is part of Hooke.
6 # Hooke is free software: you can redistribute it and/or modify it
7 # under the terms of the GNU Lesser General Public License as
8 # published by the Free Software Foundation, either version 3 of the
9 # License, or (at your option) any later version.
11 # Hooke is distributed in the hope that it will be useful, but WITHOUT
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14 # Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with Hooke. If not, see
18 # <http://www.gnu.org/licenses/>.
20 """This plugin contains example commands to teach how to write an
21 Hooke plugin, including description of main Hooke internals.
24 from numpy import arange
26 from ..command import Command, Argument, Failure
27 from ..playlist import FilePlaylist
28 from ..plugin import Plugin
31 class TutorialPlugin (Plugin):
32 """An example plugin explaining how to code plugins.
34 Unlike previous versions of Hooke, the class name is no longer
35 important. Plugins identify themselves to
36 :func:`hooke.util.pluggable.construct_graph` by being subclasses
37 of :class:`hooke.plugin.Plugin`. However, for consistency we
38 suggest the following naming scheme, show here for the 'tutorial'
41 =========== ==============
42 module file tutorial.py
43 class name TutorialPlugin
45 =========== ==============
47 To ensure filename sanity,
48 :func:`hooke.util.pluggable.construct_graph` requires that
49 :attr:`name` does match the submodule name, but don't worry,
50 you'll get a clear exception message if you make a mistake.
53 """TutorialPlugin initialization code.
55 We call our base class' :meth:`__init__` and setup
58 # This is the plugin initialization. When Hooke starts and
59 # the plugin is loaded, this function is executed. If there
60 # is something you need to do when Hooke starts, code it in
62 print 'I am the Tutorial plugin initialization!'
64 # This super() call similar to the old-style
66 # but super() is more robust under multiple inheritance.
67 # See Guido's introduction:
68 # http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
69 # And the related PEPs:
70 # http://www.python.org/dev/peps/pep-0253/
71 # http://www.python.org/dev/peps/pep-3135/
72 super(TutorialPlugin, self).__init__(name='tutorial')
74 # We want :meth:`commands` to return a list of
75 # :class:`hooke.command.Command` instances. Rather than
76 # instantiate the classes for each call to :meth:`commands`,
77 # we instantiate them in a list here, and rely on
78 # :meth:`hooke.plugin.Plugin.commands` to return copies of
80 self._commands = [] #CommandInit]
83 def dependencies(self):
84 """Return a list of names of :class:`hooke.plugin.Plugin`\s we
87 Some plugins use features from other plugins. Hooke makes sure that
88 plugins are configured in topological order and that no plugin is
89 enabled if it is missing dependencies.
92 #Here we initialize a local configuration variable; see plotmanip_absvalue() for explanation.
93 self.config['tutorial_absvalue']=0
95 def do_nothing(self,args):
97 This is a boring but working example of an actual Hooke command.
98 A Hooke command is a function of the xxxxCommands class, which is ALWAYS defined
101 def do_nameofcommand(self,args)
103 *do_ is needed to make Hooke understand this function is a command
104 *nameofcommand is how the command will be called in the Hooke command line.
106 *args is ALWAYS needed (otherwise Hooke will crash executing the command). We will see
109 Note that if you now start Hooke with this plugin activated and you type in the Hooke command
110 line "help nothing" you will see this very text as output. So the help of a command is a
111 string comment below the function definition, like this one.
113 Commands usually return None.
115 print 'I am a Hooke command. I do nothing.'
117 def do_printargs(self,args):
119 This command prints the args you give to it.
120 args is always a string, that contains everything you write after the command.
121 So if you issue "mycommand blah blah 12345" args is "blah blah 12345".
123 Again, args is needed in the definition even if your command does not use it.
125 print 'You gave me those args: '+args
127 def help_tutorial(self):
129 This is a help function.
130 If you want a help function for something that is not a command, you can write a help
131 function like this. Calling "help tutorial" will execute this function.
133 print 'You called help_tutorial()'
135 def do_environment(self,args):
137 This plugin contains a panoramic of the Hooke command line environment variables,
138 and prints their current value.
142 TYPE: [ curve.HookeCurve ], len=variable
143 contains the actual playlist of Hooke curve objects.
144 Each HookeCurve object represents a reference to a data file.
145 We will see later in detail how do they work.
147 print 'current_list length:',len(self.current_list)
148 print 'current_list 0th:',self.current_list[0]
152 contains the index of
153 the current curve in the playlist
155 print 'pointer: ',self.pointer
158 TYPE: curve.HookeCurve
159 contains the current curve displayed.
160 We will see later how it works.
162 print 'current:',self.current
165 TYPE: [ curve.PlotObject ], len=1,2
166 contains the current default plots.
167 Each PlotObject contains all info needed to display
168 the plot: apart from the data vectors, the title, destination
170 Usually self.plots[0] is the default topmost plot, self.plots[1] is the
171 accessory bottom plot.
173 print 'plots:',self.plots
176 TYPE: { string:anything }
177 contains the current Hooke configuration variables, in form of a dictionary.
179 print 'config:',self.config
183 Contains the ordered plot manipulation functions.
184 These functions are called to modify the default plot by default before it is plotted.
185 self.plots contains the plot passed through the plot manipulators.
186 We will see it better later.
187 *YOU SHOULD NEVER MODIFY THAT*
189 print 'plotmanip: ',self.plotmanip
193 Contains the plot reading drivers.
194 *YOU SHOULD NEVER MODIFY THAT*
196 print 'drivers: ',self.drivers
200 Contains the wx Frame of the GUI.
201 ***NEVER, EVER TOUCH THAT.***
203 print 'frame: ',self.frame
205 '''self.list_of_events
206 TYPE: { string:wx.Event }
207 Contains the wx.Events to communicate with the GUI.
208 Usually not necessary to use it, unless you want
209 to create a GUI plugin.
211 print 'list of events:',self.list_of_events
213 '''self.events_from_gui
215 Contains the Queue where data from the GUI is put.
216 Usually not necessary to use it, unless you want
217 to create a GUI plugin.
219 print 'events from gui:',self.events_from_gui
221 '''self.playlist_saved
222 TYPE: Int (0/1) ; Boolean
223 Flag that tells if the playlist has been saved or not.
225 print 'playlist saved:',self.playlist_saved
227 '''self.playlist_name
229 Name of current playlist
231 print 'playlist name:',self.playlist_name
234 TYPE: Int (0/1) ; Boolean
235 Flag that tells if the playlist has been saved or not.
237 print 'notes saved:',self.notes_saved
240 def do_myfirstplot(self,args):
242 In this function, we see how to create a PlotObject and send it to the screen.
243 ***Read the code of PlotObject in curve.py before!***.
246 #We generate some interesting data to plot for this example.
247 xdata1=arange(-5,5,0.1)
248 xdata2=arange(-5,5,0.1)
249 ydata1=[item**2 for item in xdata1]
250 ydata2=[item**3 for item in xdata2]
253 #The PlotObject class lives in the curve library.
254 myplot=lhc.PlotObject()
256 The *data* of the plot live in the .vectors list.
258 plot.vectors is a multidimensional array:
261 plot.vectors[2]=sett3
264 2 curves in a x,y plot are:
265 [[[x1],[y1]],[[x2],[y2]]]
268 [[[1,2,3,4],[10,20,30,40]],[[3,6,9,12],[30,60,90,120]]]
269 x1 = self.vectors[0][0]
270 y1 = self.vectors[0][1]
271 x2 = self.vectors[1][0]
272 y2 = self.vectors[1][1]
274 #Pour 0-th dataset into myplot:
275 myplot.add_set(xdata1,ydata1)
277 #Pour 1-st dataset into myplot:
278 myplot.add_set(xdata2,ydata2)
280 #Add units to x and y axes
281 #units=[string, string]
282 myplot.units=['x axis','y axis']
284 #Where do we want the plot? 0=top, 1=bottom
287 '''Send it to the GUI.
288 Note that you *have* to encapsulate it into a list, so you
289 have to send [myplot], not simply myplot.
291 You can also send more two plots at once
292 self.send_plot([plot1,plot2])
294 self._send_plot([myplot])
297 def do_myfirstscatter(self,args):
299 How to draw a scatter plot.
301 #We generate some interesting data to plot for this example.
302 xdata1=arange(-5,5,1)
303 xdata2=arange(-5,5,1)
304 ydata1=[item**2 for item in xdata1]
305 ydata2=[item**3 for item in xdata2]
307 myplot=lhc.PlotObject()
308 myplot.add_set(xdata1,ydata1)
309 myplot.add_set(xdata2,ydata2)
312 #Add units to x and y axes
313 myplot.units=['x axis','y axis']
315 #Where do we want the plot? 0=top, 1=bottom
318 '''None=standard line plot
319 'scatter'=scatter plot
320 By default, the styles attribute is an empty list. If you
321 want to define a scatter plot, you must define all other
322 plots as None or 'scatter', depending on what you want.
324 Here we define the second set to be plotted as scatter,
325 and the first to be plotted as line.
327 Here we define also the colors to be the default Matplotlib colors
329 myplot.styles=[None,'scatter']
330 myplot.colors=[None,None]
331 self._send_plot([myplot])
334 def do_clickaround(self,args):
336 Here we click two points on the curve and take some parameters from the points
341 points = self._measure_N_points(N=Int, whatset=Int)
342 *N = number of points to measure(1...n)
343 *whatset = data set to measure (0,1...n)
344 *points = a list of ClickedPoint objects, one for each point requested
346 points=self._measure_N_points(N=2,whatset=1)
347 print 'You clicked the following points.'
350 These are the absolute coordinates of the
354 print 'Absolute coordinates:'
355 print points[0].absolute_coords
356 print points[1].absolute_coords
360 These are the coordinates of the points
361 clicked, remapped on the graph.
362 Hooke looks at the graph point which X
363 coordinate is next to the X coordinate of
364 the point measured, and uses that point
365 as the actual clicked point.
368 print 'Coordinates on the graph:'
369 print points[0].graph_coords
370 print points[1].graph_coords
374 These are the indexes of the clicked points
375 on the dataset vector.
377 print 'Index of points on the graph:'
378 print points[0].index
379 print points[1].index
382 def help_thedifferentplots(self):
384 The *three* different default plots you should be familiar with
387 Each plot contains of course the respective data in their
388 vectors attribute, so here you learn also which data access for
392 1. THE RAW, CURRENT PLOTS
396 Contains the current curve.HookeCurve container object.
397 A HookeCurve object defines only two default attributes:
399 * self.current.path = string
400 The path of the current displayed curve
402 * self.current.curve = curve.Driver
403 The curve object. This is not only generated by the driver,
404 this IS a driver instance in itself.
405 This means that in self.current.curve you can access the
406 specific driver APIs, if you know them.
408 And defines only one method:
409 * self.current.identify()
410 Fills in the self.current.curve object.
411 See in the cycling tutorial.
414 The REAL curve data actually lives in:
416 * self.current.curve.default_plots() = [ libhooke.PlotObject ]
417 Contains the raw PlotObject-s, as "spitted out" by the driver, without any
419 This is as close to the raw data as Hooke gets.
421 One or two plots can be spit out; they are always enclosed in a list.
424 Methods of self.current.curve are:
427 * self.current.curve.is_me()
428 (Used by identify() only.)
430 * self.current.curve.close_all()
431 Closes all driver open files; see the cycling tutorial.
435 2. THE PROCESSED, DEFAULT PLOT
437 The plot that is spitted out by the driver is *not* the usual default plot
438 that is displayed by calling "plot" at the Hooke prompt.
440 This is because the raw, driver-generated plot is usually *processed* by so called
441 *plot processing* functions. We will see in the tutorial how to define
444 For example, in force spectroscopy force curves, raw data are automatically corrected
445 for deflection. Other data can be, say, filtered by default.
447 The default plots are accessible in
448 self.plots = [ libhooke.PlotObject ]
450 self.plots[0] is usually the topmost plot
451 self.plots[1] is usually the bottom plot (if present)
455 3. THE PLOT DISPLAYED RIGHT NOW.
457 Sometimes the plots you are displaying *right now* is different from the previous
458 two. You may have a fit trace, you may have issued some command that spits out
459 a custom plot and you want to rework that, whatever.
461 You can obtain in any moment the plot currently displayed by Hooke by issuing
463 PlotObject = self._get_displayed_plot(dest)
470 def do_cycling(self,args):
472 Here we cycle through our playlist and print some info on the curves we find.
473 Cycling through the playlist needs a bit of care to avoid memory leaks and dangling
476 Look at the source code for more information.
479 def things_when_cycling(item):
481 We encapsulate here everything has to open the actual curve file.
482 By doing it all here, we avoid to do acrobacies when deleting objects etc.
483 in the main loop: we do the dirty stuff here.
489 This method looks for the correct driver in self.drivers to use;
490 and puts the curve content in the .curve attribute.
491 Basically, until identify() is called, the HookeCurve object
492 is just an empty shell. When identify() is called (usually by
493 the Hooke plot routine), the HookeCurve object is "filled" with
497 item.identify(self.drivers)
500 After the identify(), item.curve contains the curve, and item.curve.default_plots() behaves exactly like
501 self.current.curve.default_plots() -but for the given item.
503 itplot=item.curve.default_plots()
505 print 'length of X1 vector:',len(itplot[0].vectors[0][0]) #just to show something
508 The following three lines are a magic spell you HAVE to do
509 before closing the function.
510 (Otherwise you will be plagued by unpredicatable, system-dependent bugs.)
512 item.curve.close_all() #Avoid open files dangling
513 del item.curve #Avoid memory leaks
514 del item #Just be paranoid. Don't ask.
520 for item in self.current_list:
521 print 'Looking at curve ',c,'of',len(self.current_list)
522 things_when_cycling(item)
529 def plotmanip_absvalue(self, plot, current, customvalue=None):
531 This function defines a PLOT MANIPULATOR.
532 A plot manipulator is a function that takes a plot in input, does something to the plot
533 and returns the modified plot in output.
534 The function, once plugged, gets automatically called everytime self.plots is updated
536 For example, in force spectroscopy force curves, raw data are automatically corrected
537 for deflection. Other data can be, say, filtered by default.
539 To create and activate a plot manipulator you have to:
540 * Write a function (like this) which name starts with "plotmanip_" (just like commands
542 * The function must support four arguments:
545 current : (usually not used, deprecated)
546 customvalue=None : a variable containing custom value(s) you need for your plot manipulators.
547 * The function must return a plot object.
548 * Add an entry in hooke.conf: if your function is "plotmanip_something" you will have
549 to add <something/> in the plotmanips section: example
558 Important: Plot manipulators are *in pipe*: each plot manipulator output becomes the input of the next one.
559 The order in hooke.conf *is the order* in which plot manipulators are connected, so in the example above
561 self.current.curve.default_plots() --> detriggerize --> correct --> median --> something --> self.plots
565 Here we see what is in a configuration variable to enable/disable the plot manipulator as user will using
566 the Hooke "set" command.
567 Typing "set tutorial_absvalue 0" disables the plot manipulator; typing "set tutorial_absvalue 1" will enable it.
569 if not self.config['tutorial_absvalue']:
572 #We do something to the plot, for demonstration's sake
573 #If we needed variables, we would have used customvalue.
574 plot.vectors[0][1]=[abs(i) for i in plot.vectors[0][1]]
575 plot.vectors[1][1]=[abs(i) for i in plot.vectors[1][1]]
577 #Return the plot object.
582 #how to add lines to an existing plot!!