Add dependency checking to hooke.plugin.load_graph
[hooke.git] / hooke / plugin / tutorial.py
1 # Copyright (C) 2007-2010 Massimo Sandal <devicerandom@gmail.com>
2 #                         W. Trevor King <wking@drexel.edu>
3 #
4 # This file is part of Hooke.
5 #
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.
10 #
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.
15 #
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/>.
19
20 """This plugin contains example commands to teach how to write an
21 Hooke plugin, including description of main Hooke internals.
22 """
23
24 from numpy import arange
25
26 from ..command import Command, Argument, Failure
27 from ..playlist import FilePlaylist
28 from ..plugin import Plugin
29
30
31 class TutorialPlugin (Plugin):
32     """An example plugin explaining how to code plugins.
33
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'
39     plugin:
40
41     ===========  ==============
42     module file  tutorial.py
43     class name   TutorialPlugin
44     .name        'tutorial'
45     ===========  ==============
46
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.
51     """
52     def __init__(self):
53         """TutorialPlugin initialization code.
54
55         We call our base class' :meth:`__init__` and setup
56         :attr:`_commands`.
57         """
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
61         # this function.
62         print 'I am the Tutorial plugin initialization!'
63
64         # This super() call similar to the old-style
65         #   Plugin.__init__
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')
73
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
79         # that list.
80         self._commands = [] #CommandInit]
81
82
83     def dependencies(self):
84         """Return a list  of names of :class:`hooke.plugin.Plugin`\s we
85         require.
86
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.
90         """
91         return ['vclamp']
92         #Here we initialize a local configuration variable; see plotmanip_absvalue() for explanation.
93         self.config['tutorial_absvalue']=0
94
95     def do_nothing(self,args):
96         '''
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
99         this way:
100
101         def do_nameofcommand(self,args)
102
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.
105         *self           is, well, self
106         *args           is ALWAYS needed (otherwise Hooke will crash executing the command). We will see
107                         later what args is.
108
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.
112
113         Commands usually return None.
114         '''
115         print 'I am a Hooke command. I do nothing.'
116
117     def do_printargs(self,args):
118         '''
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".
122
123         Again, args is needed in the definition even if your command does not use it.
124         '''
125         print 'You gave me those args: '+args
126
127     def help_tutorial(self):
128         '''
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.
132         '''
133         print 'You called help_tutorial()'
134
135     def do_environment(self,args):
136         '''
137         This plugin contains a panoramic of the Hooke command line environment variables,
138         and prints their current value.
139         '''
140
141         '''self.current_list
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.
146         '''
147         print 'current_list length:',len(self.current_list)
148         print 'current_list 0th:',self.current_list[0]
149
150         '''self.pointer
151         TYPE: int
152         contains the index of
153         the current curve in the playlist
154         '''
155         print 'pointer: ',self.pointer
156
157         '''self.current
158         TYPE: curve.HookeCurve
159         contains the current curve displayed.
160         We will see later how it works.
161         '''
162         print 'current:',self.current
163
164         '''self.plots
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
169         etc.
170         Usually self.plots[0] is the default topmost plot, self.plots[1] is the
171         accessory bottom plot.
172         '''
173         print 'plots:',self.plots
174
175         '''self.config
176         TYPE: { string:anything }
177         contains the current Hooke configuration variables, in form of a dictionary.
178         '''
179         print 'config:',self.config
180
181         '''self.plotmanip
182         TYPE: [ function ]
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*
188         '''
189         print 'plotmanip: ',self.plotmanip
190
191         '''self.drivers
192         TYPE: [ class ]
193         Contains the plot reading drivers.
194         *YOU SHOULD NEVER MODIFY THAT*
195         '''
196         print 'drivers: ',self.drivers
197
198         '''self.frame
199         TYPE: wx.Frame
200         Contains the wx Frame of the GUI.
201         ***NEVER, EVER TOUCH THAT.***
202         '''
203         print 'frame: ',self.frame
204
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.
210         '''
211         print 'list of events:',self.list_of_events
212
213         '''self.events_from_gui
214         TYPE: Queue.Queue
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.
218         '''
219         print 'events from gui:',self.events_from_gui
220
221         '''self.playlist_saved
222         TYPE: Int (0/1) ; Boolean
223         Flag that tells if the playlist has been saved or not.
224         '''
225         print 'playlist saved:',self.playlist_saved
226
227         '''self.playlist_name
228         TYPE: string
229         Name of current playlist
230         '''
231         print 'playlist name:',self.playlist_name
232
233         '''self.notes_saved
234         TYPE: Int (0/1) ; Boolean
235         Flag that tells if the playlist has been saved or not.
236         '''
237         print 'notes saved:',self.notes_saved
238
239
240     def do_myfirstplot(self,args):
241         '''
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!***.
244         '''
245
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]
251
252         #Create the object.
253         #The PlotObject class lives in the curve library.
254         myplot=lhc.PlotObject()
255         '''
256         The *data* of the plot live in the .vectors list.
257
258         plot.vectors is a multidimensional array:
259         plot.vectors[0]=set1
260         plot.vectors[1]=set2
261         plot.vectors[2]=sett3
262         etc.
263
264         2 curves in a x,y plot are:
265         [[[x1],[y1]],[[x2],[y2]]]
266         for example:
267             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]
273         '''
274         #Pour 0-th dataset into myplot:
275         myplot.add_set(xdata1,ydata1)
276
277         #Pour 1-st dataset into myplot:
278         myplot.add_set(xdata2,ydata2)
279
280         #Add units to x and y axes
281         #units=[string, string]
282         myplot.units=['x axis','y axis']
283
284         #Where do we want the plot? 0=top, 1=bottom
285         myplot.destination=1
286
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.
290
291         You can also send more two plots at once
292         self.send_plot([plot1,plot2])
293         '''
294         self._send_plot([myplot])
295
296
297     def do_myfirstscatter(self,args):
298         '''
299         How to draw a scatter plot.
300         '''
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]
306
307         myplot=lhc.PlotObject()
308         myplot.add_set(xdata1,ydata1)
309         myplot.add_set(xdata2,ydata2)
310
311
312         #Add units to x and y axes
313         myplot.units=['x axis','y axis']
314
315         #Where do we want the plot? 0=top, 1=bottom
316         myplot.destination=1
317
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.
323
324         Here we define the second set to be plotted as scatter,
325         and the first to be plotted as line.
326
327         Here we define also the colors to be the default Matplotlib colors
328         '''
329         myplot.styles=[None,'scatter']
330         myplot.colors=[None,None]
331         self._send_plot([myplot])
332
333
334     def do_clickaround(self,args):
335         '''
336         Here we click two points on the curve and take some parameters from the points
337         we have clicked.
338         '''
339
340         '''
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
345         '''
346         points=self._measure_N_points(N=2,whatset=1)
347         print 'You clicked the following points.'
348
349         '''
350         These are the absolute coordinates of the
351         point clicked.
352         [float, float] = x,y
353         '''
354         print 'Absolute coordinates:'
355         print points[0].absolute_coords
356         print points[1].absolute_coords
357         print
358
359         '''
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.
366         [float, float] = x,y
367         '''
368         print 'Coordinates on the graph:'
369         print points[0].graph_coords
370         print points[1].graph_coords
371         print
372
373         '''
374         These are the indexes of the clicked points
375         on the dataset vector.
376         '''
377         print 'Index of points on the graph:'
378         print points[0].index
379         print points[1].index
380
381
382     def help_thedifferentplots(self):
383         '''
384         The *three* different default plots you should be familiar with
385         in Hooke.
386
387         Each plot contains of course the respective data in their
388         vectors attribute, so here you learn also which data access for
389         each situation.
390         '''
391         print '''
392         1. THE RAW, CURRENT PLOTS
393
394         self.current
395         ---
396         Contains the current curve.HookeCurve container object.
397         A HookeCurve object defines only two default attributes:
398
399         * self.current.path = string
400         The path of the current displayed curve
401
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.
407
408         And defines only one method:
409         * self.current.identify()
410         Fills in the self.current.curve object.
411         See in the cycling tutorial.
412
413         *****
414         The REAL curve data actually lives in:
415         ---
416         * self.current.curve.default_plots() = [ libhooke.PlotObject ]
417         Contains the raw PlotObject-s, as "spitted out" by the driver, without any
418         intervention.
419         This is as close to the raw data as Hooke gets.
420
421         One or two plots can be spit out; they are always enclosed in a list.
422         *****
423
424         Methods of self.current.curve are:
425         ---
426
427         * self.current.curve.is_me()
428         (Used by identify() only.)
429
430         * self.current.curve.close_all()
431         Closes all driver open files; see the cycling tutorial.
432         '''
433
434         print '''
435         2. THE PROCESSED, DEFAULT PLOT
436
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.
439
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
442         them.
443
444         For example, in force spectroscopy force curves, raw data are automatically corrected
445         for deflection. Other data can be, say, filtered by default.
446
447         The default plots are accessible in
448         self.plots = [ libhooke.PlotObject ]
449
450         self.plots[0] is usually the topmost plot
451         self.plots[1] is usually the bottom plot (if present)
452         '''
453
454         print '''
455         3. THE PLOT DISPLAYED RIGHT NOW.
456
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.
460
461         You can obtain in any moment the plot currently displayed by Hooke by issuing
462
463         PlotObject = self._get_displayed_plot(dest)
464         * dest = Int (0/1)
465         dest=0 : top plot
466         dest=1 : bottom plot
467         '''
468
469
470     def do_cycling(self,args):
471         '''
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
474         open files...
475
476         Look at the source code for more information.
477         '''
478
479         def things_when_cycling(item):
480             '''
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.
484             '''
485
486             '''
487             identify()
488
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
494             the actual curve.
495             '''
496
497             item.identify(self.drivers)
498
499             '''
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.
502             '''
503             itplot=item.curve.default_plots()
504
505             print 'length of X1 vector:',len(itplot[0].vectors[0][0]) #just to show something
506
507             '''
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.)
511             '''
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.
515
516             return
517
518
519         c=0
520         for item in self.current_list:
521             print 'Looking at curve ',c,'of',len(self.current_list)
522             things_when_cycling(item)
523             c+=1
524
525         return
526
527
528
529     def plotmanip_absvalue(self, plot, current, customvalue=None):
530         '''
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
535
536         For example, in force spectroscopy force curves, raw data are automatically corrected
537         for deflection. Other data can be, say, filtered by default.
538
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
541               start with "do_")
542             * The function must support four arguments:
543               self : (as usual)
544               plot : a plot object
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
550
551             <plotmanips>
552                 <detriggerize/>
553                 <correct/>
554                 <median/>
555                 <something/>
556             </plotmanips>
557
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
560             we have:
561             self.current.curve.default_plots() --> detriggerize --> correct --> median --> something --> self.plots
562         '''
563
564         '''
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.
568         '''
569         if not self.config['tutorial_absvalue']:
570             return plot
571
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]]
576
577         #Return the plot object.
578         return plot
579
580
581 #TODO IN TUTORIAL:
582 #how to add lines to an existing plot!!
583 #peaks
584 #configuration files
585 #gui plugins