Hooke(GUI)
[hooke.git] / plugins / procplots.py
1 #!/usr/bin/env python
2
3 '''
4 procplots.py
5
6 Process plots plugin for force curves.
7
8 Copyright ???? by ?
9 with modifications by Dr. Rolf Schmidt (Concordia University, Canada)
10
11 This program is released under the GNU General Public License version 2.
12 '''
13
14 import lib.libhooke as lh
15 import wxversion
16 wxversion.select(lh.WX_GOOD)
17
18 import copy
19 from numpy import arange, diff, fft, median
20 from scipy.signal import medfilt
21
22 from lib.peakspot import conv_dx
23 import lib.prettyformat
24
25 class procplotsCommands:
26
27     def _plug_init(self):
28         pass
29
30     def do_convplot(self):
31         '''
32         CONVPLOT
33         (procplots.py)
34         Plots the convolution data of the currently displayed force curve retraction.
35         ------------
36         Syntax:
37         convplot
38         '''
39
40         #need to convert the string that contains the list into a list
41         column = self.GetIntFromConfig('procplots', 'convplot', 'column')
42         convolution = eval(self.GetStringFromConfig('procplots', 'convplot', 'convolution'))
43         row = self.GetIntFromConfig('procplots', 'convplot', 'row')
44         whatset_str = self.GetStringFromConfig('procplots', 'convplot', 'whatset')
45         whatset = []
46         if whatset_str == 'extension':
47             whatset = [lh.EXTENSION]
48         if whatset_str == 'retraction':
49             whatset = [lh.RETRACTION]
50         if whatset_str == 'both':
51             whatset = [lh.EXTENSION, lh.RETRACTION]
52
53         #TODO: add option to keep previous derivplot
54         plot = self.GetDisplayedPlotCorrected()
55
56         for index in whatset:
57             conv_curve = copy.deepcopy(plot.curves[index])
58             #Calculate convolution
59             conv_curve.y = conv_dx(plot.curves[index].y, convolution)
60
61             conv_curve.destination.column = column
62             conv_curve.destination.row = row
63             conv_curve.title = 'Convolution'
64             plot.curves.append(conv_curve)
65
66         #warn if no flattening has been done.
67         if not self.AppliesPlotmanipulator('flatten'):
68             self.AppendToOutput('Flatten manipulator was not applied. Processing was done without flattening.')
69             self.AppendToOutput('Enable the flatten plotmanipulator for better results.')
70
71         self.UpdatePlot(plot)
72
73     def do_derivplot(self):
74         '''
75         Plots the discrete differentiation of the currently displayed force curve.
76         '''
77         column = self.GetIntFromConfig('procplots', 'derivplot', 'column')
78         row = self.GetIntFromConfig('procplots', 'derivplot', 'row')
79         #TODO: what os select good for?
80         select = self.GetBoolFromConfig('procplots', 'derivplot', 'select')
81         whatset_str = self.GetStringFromConfig('procplots', 'derivplot', 'whatset')
82         whatset = []
83         if whatset_str == 'extension':
84             whatset = [lh.EXTENSION]
85         if whatset_str == 'retraction':
86             whatset = [lh.RETRACTION]
87         if whatset_str == 'both':
88             whatset = [lh.EXTENSION, lh.RETRACTION]
89
90         #TODO: add option to keep previous derivplot
91         plot = self.GetDisplayedPlotCorrected()
92
93         for index in whatset:
94             deriv_curve = copy.deepcopy(plot.curves[index])
95             deriv_curve.x = deriv_curve.x[:-1]
96             deriv_curve.y = diff(deriv_curve.y).tolist()
97
98             deriv_curve.destination.column = column
99             deriv_curve.destination.row = row
100             deriv_curve.title = 'Discrete differentiation'
101             deriv_curve.units.y += ' ' + deriv_curve.units.x + '-1'
102             plot.curves.append(deriv_curve)
103
104         self.UpdatePlot(plot)
105
106     def do_replot(self):
107         '''
108         Replots the current force curve from scratch eliminating any secondary plots.
109         '''
110         self.UpdatePlot()
111
112     def do_subtplot(self):
113         '''
114         SUBTPLOT
115         (procplots.py plugin)
116         Plots the difference between retraction and extension of the currently displayed curve
117         -------
118         Syntax: subtplot
119         '''
120
121         #TODO: add option to keep previous subtplot
122         plot = self.GetDisplayedPlotCorrected()
123
124         extension = plot.curves[lh.EXTENSION]
125         retraction = plot.curves[lh.RETRACTION]
126
127         extension, retraction = self.subtract_curves(extension, retraction)
128
129         self.UpdatePlot(plot)
130
131     def subtract_curves(self, minuend, subtrahend):
132         '''
133         calculates: difference = minuend - subtrahend
134         (usually:              extension - retraction
135         '''
136
137         #we want the same number of points for minuend and subtrahend
138         #TODO: is this not already done when normalizing in the driver?
139         maxpoints_tot = min(len(minuend.x), len(subtrahend.x))
140         minuend.x = minuend.x[0:maxpoints_tot]
141         minuend.y = minuend.y[0:maxpoints_tot]
142         subtrahend.x = subtrahend.x[0:maxpoints_tot]
143         subtrahend.y = subtrahend.y[0:maxpoints_tot]
144
145         subtrahend.y = [y_subtrahend - y_minuend for y_subtrahend, y_minuend in zip(subtrahend.y, minuend.y)]
146         minuend.y = [0] * len(minuend.x)
147
148         return minuend, subtrahend
149
150 #-----PLOT MANIPULATORS
151     def plotmanip_median(self, plot, current, customvalue=False):
152         '''
153         does the median of the y values of a plot
154         '''
155         median_filter = self.GetIntFromConfig('procplots', 'median')
156         if median_filter == 0:
157             return plot
158
159         if float(median_filter) / 2 == int(median_filter) / 2:
160             median_filter += 1
161
162         for curve in plot.curves:
163             curve.y = medfilt(curve.y, median_filter).tolist()
164
165         return plot
166
167     def plotmanip_correct(self, plot, current, customvalue=False):
168         '''
169         does the correction for the deflection for a force spectroscopy curve.
170         Assumes that:
171         - the current plot has a deflection() method that returns a vector of values
172         - the deflection() vector is as long as the X of extension + the X of retraction
173         - plot.vectors[0][0] is the X of extension curve
174         - plot.vectors[1][0] is the X of retraction curve
175
176         FIXME: both this method and the picoforce driver have to be updated, deflection() must return
177         a more sensible data structure!
178         '''
179         #use only for force spectroscopy experiments!
180         if current.driver.experiment != 'smfs':
181             return plot
182
183         if not customvalue:
184             customvalue = self.GetBoolFromConfig('procplots', 'correct')
185         if not customvalue:
186             return plot
187
188         defl_ext, defl_ret = current.driver.deflection()
189
190         plot.curves[lh.EXTENSION].x = [(zpoint - deflpoint) for zpoint,deflpoint in zip(plot.curves[lh.EXTENSION].x, defl_ext)]
191         plot.curves[lh.RETRACTION].x = [(zpoint - deflpoint) for zpoint,deflpoint in zip(plot.curves[lh.RETRACTION].x, defl_ret)]
192
193         return plot
194
195     def plotmanip_centerzero(self, plot, current, customvalue=False):
196         '''
197         Centers the force curve so the median (the free level) corresponds to 0 N
198         '''
199         #use only for force spectroscopy experiments!
200         if current.driver.experiment != 'smfs':
201             return plot
202
203         if not customvalue:
204             customvalue = self.GetBoolFromConfig('procplots', 'centerzero')
205         if not customvalue:
206             return plot
207
208         levelapp = float(median(plot.curves[lh.EXTENSION].y))
209         levelret = float(median(plot.curves[lh.RETRACTION].y))
210
211         level = (levelapp + levelret)/2
212
213         plot.curves[lh.EXTENSION].y = [i-level for i in plot.curves[lh.EXTENSION].y]
214         plot.curves[lh.RETRACTION].y = [i-level for i in plot.curves[lh.RETRACTION].y]
215
216         return plot
217
218 #FFT---------------------------
219     def fft_plot(self, curve, boundaries=[0, -1]):
220         '''
221         calculates the fast Fourier transform for the selected vector in the plot
222         '''
223
224         fftlen = len(curve.y[boundaries[0]:boundaries[1]]) / 2 #need just 1/2 of length
225         curve.x = arange(1, fftlen).tolist()
226
227         try:
228             curve.y = abs(fft(curve.y[boundaries[0]:boundaries[1]])[1:fftlen]).tolist()
229         except TypeError: #we take care of newer NumPy (1.0.x)
230             curve.y = abs(fft.fft(curve.y[boundaries[0]:boundaries[1]])[1:fftlen]).tolist()
231
232         return curve
233
234     def do_fft(self):
235         '''
236         FFT
237         (procplots.py plugin)
238         Plots the fast Fourier transform of the selected plot
239         ---
240         Syntax: fft [top,bottom] [select] [0,1...]
241
242         By default, fft performs the Fourier transform on all the 0-th data set on the
243         top plot.
244
245         [top, bottom]: which plot is the data set to fft (default: top)
246         [select]: you pick up two points on the plot and fft only the segment between
247         [0,1,...]: which data set on the selected plot you want to fft (default: 0)
248         '''
249
250         column = self.GetIntFromConfig('procplots', 'fft', 'column')
251         row = self.GetIntFromConfig('procplots', 'fft', 'row')
252         select = self.GetBoolFromConfig('procplots', 'fft', 'select')
253         whatset_str = self.GetStringFromConfig('procplots', 'fft', 'whatset')
254         whatset = []
255         if whatset_str == 'extension':
256             whatset = [lh.EXTENSION]
257         if whatset_str == 'retraction':
258             whatset = [lh.RETRACTION]
259         if whatset_str == 'both':
260             whatset = [lh.EXTENSION, lh.RETRACTION]
261
262         if select:
263             points = self._measure_N_points(N=2, message='Please select a region by clicking on the start and the end point.', whatset=lh.RETRACTION)
264             boundaries = [points[0].index, points[1].index]
265             boundaries.sort()
266         else:
267             boundaries = [0, -1]
268
269         #TODO: add option to keep previous FFT
270         plot = self.GetDisplayedPlotCorrected()
271
272         for index in whatset:
273             fft_curve = self.fft_plot(copy.deepcopy(plot.curves[index]), boundaries)
274
275             fft_curve.decimals.x = 3
276             fft_curve.decimals.y = 0
277             fft_curve.destination.column = column
278             fft_curve.destination.row = row
279             fft_curve.label = plot.curves[index].label
280             fft_curve.legend = True
281             fft_curve.prefix.x = lib.prettyformat.get_prefix(max(fft_curve.x))
282             fft_curve.prefix.y = lib.prettyformat.get_prefix(max(fft_curve.y))
283             fft_curve.title = 'FFT'
284             fft_curve.units.x = 'Hz'
285             fft_curve.units.y = 'power'
286             plot.curves.append(fft_curve)
287
288         self.UpdatePlot(plot)