Ran update_copyright.py
[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 import numpy as np
25
26 from .. import curve as lhc
27
28 '''
29 SYNTAX OF DATA TYPE DECLARATION:
30     type = type of object
31     [ type ] = list containing objects of type
32     {typekey:typearg} = dictionary with keys of type typekey and args of type typearg
33     ( type ) = tuple containing objects of type
34 '''
35
36
37 class tutorialCommands(object):
38     '''
39     Here we define the class containing all the Hooke commands we want to define
40     in the plugin.
41
42     Notice the class name!!
43     The syntax is filenameCommands. That is, if your plugin is pluggy.py, your class
44     name is pluggyCommands.
45
46     Otherwise, the class will be ignored by Hooke.
47     '''
48
49     def _plug_init(self):
50         '''
51         This is the plugin initialization.
52         When Hooke starts and the plugin is loaded, this function is executed.
53         If there is something you need to do when Hooke starts, code it in this function.
54         '''
55         print 'I am the Tutorial plugin initialization!'
56
57         #Here we initialize a local configuration variable; see plotmanip_absvalue() for explanation.
58         self.config['tutorial_absvalue']=0
59         pass
60
61     def do_nothing(self,args):
62         '''
63         This is a boring but working example of an actual Hooke command.
64         A Hooke command is a function of the xxxxCommands class, which is ALWAYS defined
65         this way:
66
67         def do_nameofcommand(self,args)
68
69         *do_            is needed to make Hooke understand this function is a command
70         *nameofcommand  is how the command will be called in the Hooke command line.
71         *self           is, well, self
72         *args           is ALWAYS needed (otherwise Hooke will crash executing the command). We will see
73                         later what args is.
74
75         Note that if you now start Hooke with this plugin activated and you type in the Hooke command
76         line "help nothing" you will see this very text as output. So the help of a command is a
77         string comment below the function definition, like this one.
78
79         Commands usually return None.
80         '''
81         print 'I am a Hooke command. I do nothing.'
82
83     def do_printargs(self,args):
84         '''
85         This command prints the args you give to it.
86         args is always a string, that contains everything you write after the command.
87         So if you issue "mycommand blah blah 12345" args is "blah blah 12345".
88
89         Again, args is needed in the definition even if your command does not use it.
90         '''
91         print 'You gave me those args: '+args
92
93     def help_tutorial(self):
94         '''
95         This is a help function.
96         If you want a help function for something that is not a command, you can write a help
97         function like this. Calling "help tutorial" will execute this function.
98         '''
99         print 'You called help_tutorial()'
100
101     def do_environment(self,args):
102         '''
103         This plugin contains a panoramic of the Hooke command line environment variables,
104         and prints their current value.
105         '''
106
107         '''self.current_list
108         TYPE: [ curve.HookeCurve ], len=variable
109         contains the actual playlist of Hooke curve objects.
110         Each HookeCurve object represents a reference to a data file.
111         We will see later in detail how do they work.
112         '''
113         print 'current_list length:',len(self.current_list)
114         print 'current_list 0th:',self.current_list[0]
115
116         '''self.pointer
117         TYPE: int
118         contains the index of
119         the current curve in the playlist
120         '''
121         print 'pointer: ',self.pointer
122
123         '''self.current
124         TYPE: curve.HookeCurve
125         contains the current curve displayed.
126         We will see later how it works.
127         '''
128         print 'current:',self.current
129
130         '''self.plots
131         TYPE: [ curve.PlotObject ], len=1,2
132         contains the current default plots.
133         Each PlotObject contains all info needed to display
134         the plot: apart from the data vectors, the title, destination
135         etc.
136         Usually self.plots[0] is the default topmost plot, self.plots[1] is the
137         accessory bottom plot.
138         '''
139         print 'plots:',self.plots
140
141         '''self.config
142         TYPE: { string:anything }
143         contains the current Hooke configuration variables, in form of a dictionary.
144         '''
145         print 'config:',self.config
146
147         '''self.plotmanip
148         TYPE: [ function ]
149         Contains the ordered plot manipulation functions.
150         These functions are called to modify the default plot by default before it is plotted.
151         self.plots contains the plot passed through the plot manipulators.
152         We will see it better later.
153         *YOU SHOULD NEVER MODIFY THAT*
154         '''
155         print 'plotmanip: ',self.plotmanip
156
157         '''self.drivers
158         TYPE: [ class ]
159         Contains the plot reading drivers.
160         *YOU SHOULD NEVER MODIFY THAT*
161         '''
162         print 'drivers: ',self.drivers
163
164         '''self.frame
165         TYPE: wx.Frame
166         Contains the wx Frame of the GUI.
167         ***NEVER, EVER TOUCH THAT.***
168         '''
169         print 'frame: ',self.frame
170
171         '''self.list_of_events
172         TYPE: { string:wx.Event }
173         Contains the wx.Events to communicate with the GUI.
174         Usually not necessary to use it, unless you want
175         to create a GUI plugin.
176         '''
177         print 'list of events:',self.list_of_events
178
179         '''self.events_from_gui
180         TYPE: Queue.Queue
181         Contains the Queue where data from the GUI is put.
182         Usually not necessary to use it, unless you want
183         to create a GUI plugin.
184         '''
185         print 'events from gui:',self.events_from_gui
186
187         '''self.playlist_saved
188         TYPE: Int (0/1) ; Boolean
189         Flag that tells if the playlist has been saved or not.
190         '''
191         print 'playlist saved:',self.playlist_saved
192
193         '''self.playlist_name
194         TYPE: string
195         Name of current playlist
196         '''
197         print 'playlist name:',self.playlist_name
198
199         '''self.notes_saved
200         TYPE: Int (0/1) ; Boolean
201         Flag that tells if the playlist has been saved or not.
202         '''
203         print 'notes saved:',self.notes_saved
204
205
206     def do_myfirstplot(self,args):
207         '''
208         In this function, we see how to create a PlotObject and send it to the screen.
209         ***Read the code of PlotObject in curve.py before!***.
210         '''
211
212         #We generate some interesting data to plot for this example.
213         xdata1=np.arange(-5,5,0.1)
214         xdata2=np.arange(-5,5,0.1)
215         ydata1=[item**2 for item in xdata1]
216         ydata2=[item**3 for item in xdata2]
217
218         #Create the object.
219         #The PlotObject class lives in the curve library.
220         myplot=lhc.PlotObject()
221         '''
222         The *data* of the plot live in the .vectors list.
223
224         plot.vectors is a multidimensional array:
225         plot.vectors[0]=set1
226         plot.vectors[1]=set2
227         plot.vectors[2]=sett3
228         etc.
229
230         2 curves in a x,y plot are:
231         [[[x1],[y1]],[[x2],[y2]]]
232         for example:
233             x1          y1              x2         y2
234         [[[1,2,3,4],[10,20,30,40]],[[3,6,9,12],[30,60,90,120]]]
235         x1 = self.vectors[0][0]
236         y1 = self.vectors[0][1]
237         x2 = self.vectors[1][0]
238         y2 = self.vectors[1][1]
239         '''
240         #Pour 0-th dataset into myplot:
241         myplot.add_set(xdata1,ydata1)
242
243         #Pour 1-st dataset into myplot:
244         myplot.add_set(xdata2,ydata2)
245
246         #Add units to x and y axes
247         #units=[string, string]
248         myplot.units=['x axis','y axis']
249
250         #Where do we want the plot? 0=top, 1=bottom
251         myplot.destination=1
252
253         '''Send it to the GUI.
254         Note that you *have* to encapsulate it into a list, so you
255         have to send [myplot], not simply myplot.
256
257         You can also send more two plots at once
258         self.send_plot([plot1,plot2])
259         '''
260         self._send_plot([myplot])
261
262
263     def do_myfirstscatter(self,args):
264         '''
265         How to draw a scatter plot.
266         '''
267         #We generate some interesting data to plot for this example.
268         xdata1=np.arange(-5,5,1)
269         xdata2=np.arange(-5,5,1)
270         ydata1=[item**2 for item in xdata1]
271         ydata2=[item**3 for item in xdata2]
272
273         myplot=lhc.PlotObject()
274         myplot.add_set(xdata1,ydata1)
275         myplot.add_set(xdata2,ydata2)
276
277
278         #Add units to x and y axes
279         myplot.units=['x axis','y axis']
280
281         #Where do we want the plot? 0=top, 1=bottom
282         myplot.destination=1
283
284         '''None=standard line plot
285         'scatter'=scatter plot
286         By default, the styles attribute is an empty list. If you
287         want to define a scatter plot, you must define all other
288         plots as None or 'scatter', depending on what you want.
289
290         Here we define the second set to be plotted as scatter,
291         and the first to be plotted as line.
292
293         Here we define also the colors to be the default Matplotlib colors
294         '''
295         myplot.styles=[None,'scatter']
296         myplot.colors=[None,None]
297         self._send_plot([myplot])
298
299
300     def do_clickaround(self,args):
301         '''
302         Here we click two points on the curve and take some parameters from the points
303         we have clicked.
304         '''
305
306         '''
307         points = self._measure_N_points(N=Int, whatset=Int)
308         *N = number of points to measure(1...n)
309         *whatset = data set to measure (0,1...n)
310         *points = a list of ClickedPoint objects, one for each point requested
311         '''
312         points=self._measure_N_points(N=2,whatset=1)
313         print 'You clicked the following points.'
314
315         '''
316         These are the absolute coordinates of the
317         point clicked.
318         [float, float] = x,y
319         '''
320         print 'Absolute coordinates:'
321         print points[0].absolute_coords
322         print points[1].absolute_coords
323         print
324
325         '''
326         These are the coordinates of the points
327         clicked, remapped on the graph.
328         Hooke looks at the graph point which X
329         coordinate is next to the X coordinate of
330         the point measured, and uses that point
331         as the actual clicked point.
332         [float, float] = x,y
333         '''
334         print 'Coordinates on the graph:'
335         print points[0].graph_coords
336         print points[1].graph_coords
337         print
338
339         '''
340         These are the indexes of the clicked points
341         on the dataset vector.
342         '''
343         print 'Index of points on the graph:'
344         print points[0].index
345         print points[1].index
346
347
348     def help_thedifferentplots(self):
349         '''
350         The *three* different default plots you should be familiar with
351         in Hooke.
352
353         Each plot contains of course the respective data in their
354         vectors attribute, so here you learn also which data access for
355         each situation.
356         '''
357         print '''
358         1. THE RAW, CURRENT PLOTS
359
360         self.current
361         ---
362         Contains the current curve.HookeCurve container object.
363         A HookeCurve object defines only two default attributes:
364
365         * self.current.path = string
366         The path of the current displayed curve
367
368         * self.current.curve = curve.Driver
369         The curve object. This is not only generated by the driver,
370         this IS a driver instance in itself.
371         This means that in self.current.curve you can access the
372         specific driver APIs, if you know them.
373
374         And defines only one method:
375         * self.current.identify()
376         Fills in the self.current.curve object.
377         See in the cycling tutorial.
378
379         *****
380         The REAL curve data actually lives in:
381         ---
382         * self.current.curve.default_plots() = [ libhooke.PlotObject ]
383         Contains the raw PlotObject-s, as "spitted out" by the driver, without any
384         intervention.
385         This is as close to the raw data as Hooke gets.
386
387         One or two plots can be spit out; they are always enclosed in a list.
388         *****
389
390         Methods of self.current.curve are:
391         ---
392
393         * self.current.curve.is_me()
394         (Used by identify() only.)
395
396         * self.current.curve.close_all()
397         Closes all driver open files; see the cycling tutorial.
398         '''
399
400         print '''
401         2. THE PROCESSED, DEFAULT PLOT
402
403         The plot that is spitted out by the driver is *not* the usual default plot
404         that is displayed by calling "plot" at the Hooke prompt.
405
406         This is because the raw, driver-generated plot is usually *processed* by so called
407         *plot processing* functions. We will see in the tutorial how to define
408         them.
409
410         For example, in force spectroscopy force curves, raw data are automatically corrected
411         for deflection. Other data can be, say, filtered by default.
412
413         The default plots are accessible in
414         self.plots = [ libhooke.PlotObject ]
415
416         self.plots[0] is usually the topmost plot
417         self.plots[1] is usually the bottom plot (if present)
418         '''
419
420         print '''
421         3. THE PLOT DISPLAYED RIGHT NOW.
422
423         Sometimes the plots you are displaying *right now* is different from the previous
424         two. You may have a fit trace, you may have issued some command that spits out
425         a custom plot and you want to rework that, whatever.
426
427         You can obtain in any moment the plot currently displayed by Hooke by issuing
428
429         PlotObject = self._get_displayed_plot(dest)
430         * dest = Int (0/1)
431         dest=0 : top plot
432         dest=1 : bottom plot
433         '''
434
435
436     def do_cycling(self,args):
437         '''
438         Here we cycle through our playlist and print some info on the curves we find.
439         Cycling through the playlist needs a bit of care to avoid memory leaks and dangling
440         open files...
441
442         Look at the source code for more information.
443         '''
444
445         def things_when_cycling(item):
446             '''
447             We encapsulate here everything has to open the actual curve file.
448             By doing it all here, we avoid to do acrobacies when deleting objects etc.
449             in the main loop: we do the dirty stuff here.
450             '''
451
452             '''
453             identify()
454
455             This method looks for the correct driver in self.drivers to use;
456             and puts the curve content in the .curve attribute.
457             Basically, until identify() is called, the HookeCurve object
458             is just an empty shell. When identify() is called (usually by
459             the Hooke plot routine), the HookeCurve object is "filled" with
460             the actual curve.
461             '''
462
463             item.identify(self.drivers)
464
465             '''
466             After the identify(), item.curve contains the curve, and item.curve.default_plots() behaves exactly like
467             self.current.curve.default_plots() -but for the given item.
468             '''
469             itplot=item.curve.default_plots()
470
471             print 'length of X1 vector:',len(itplot[0].vectors[0][0]) #just to show something
472
473             '''
474             The following three lines are a magic spell you HAVE to do
475             before closing the function.
476             (Otherwise you will be plagued by unpredicatable, system-dependent bugs.)
477             '''
478             item.curve.close_all() #Avoid open files dangling
479             del item.curve #Avoid memory leaks
480             del item #Just be paranoid. Don't ask.
481
482             return
483
484
485         c=0
486         for item in self.current_list:
487             print 'Looking at curve ',c,'of',len(self.current_list)
488             things_when_cycling(item)
489             c+=1
490
491         return
492
493
494
495     def plotmanip_absvalue(self, plot, current, customvalue=None):
496         '''
497         This function defines a PLOT MANIPULATOR.
498         A plot manipulator is a function that takes a plot in input, does something to the plot
499         and returns the modified plot in output.
500         The function, once plugged, gets automatically called everytime self.plots is updated
501
502         For example, in force spectroscopy force curves, raw data are automatically corrected
503         for deflection. Other data can be, say, filtered by default.
504
505         To create and activate a plot manipulator you have to:
506             * Write a function (like this) which name starts with "plotmanip_" (just like commands
507               start with "do_")
508             * The function must support four arguments:
509               self : (as usual)
510               plot : a plot object
511               current : (usually not used, deprecated)
512               customvalue=None : a variable containing custom value(s) you need for your plot manipulators.
513             * The function must return a plot object.
514             * Add an entry in hooke.conf: if your function is "plotmanip_something" you will have
515               to add <something/> in the plotmanips section: example
516
517             <plotmanips>
518                 <detriggerize/>
519                 <correct/>
520                 <median/>
521                 <something/>
522             </plotmanips>
523
524             Important: Plot manipulators are *in pipe*: each plot manipulator output becomes the input of the next one.
525             The order in hooke.conf *is the order* in which plot manipulators are connected, so in the example above
526             we have:
527             self.current.curve.default_plots() --> detriggerize --> correct --> median --> something --> self.plots
528         '''
529
530         '''
531         Here we see what is in a configuration variable to enable/disable the plot manipulator as user will using
532         the Hooke "set" command.
533         Typing "set tutorial_absvalue 0" disables the plot manipulator; typing "set tutorial_absvalue 1" will enable it.
534         '''
535         if not self.config['tutorial_absvalue']:
536             return plot
537
538         #We do something to the plot, for demonstration's sake
539         #If we needed variables, we would have used customvalue.
540         plot.vectors[0][1]=[abs(i) for i in plot.vectors[0][1]]
541         plot.vectors[1][1]=[abs(i) for i in plot.vectors[1][1]]
542
543         #Return the plot object.
544         return plot
545
546
547 #TODO IN TUTORIAL:
548 #how to add lines to an existing plot!!
549 #peaks
550 #configuration files
551 #gui plugins