1 # Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation, either
8 # version 3 of the License, or (at your option) any later version.
10 # Hooke is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with Hooke. If not, see
17 # <http://www.gnu.org/licenses/>.
19 """Force spectroscopy curves filtering of flat curves
21 Other plugin dependencies:
22 procplots.py (plot processing plugin)
25 import xml.dom.minidom
30 from numpy import diff
35 import libpeakspot as lps
37 import hookecurve as lhc
40 wxversion.select(lh.WX_GOOD)
43 class flatfiltsCommands:
45 def do_flatfilt(self):
49 Filters out flat (featureless) curves of the current playlist,
50 creating a playlist containing only the curves with potential
54 flatfilt [min_npks min_deviation]
56 min_npks = minmum number of points over the deviation
59 min_deviation = minimum signal/noise ratio
62 If called without arguments, it uses default values, that
63 should work most of the times.
65 #TODO: should this be optional?
68 self.AppendToOutput('Processing playlist...')
69 self.AppendToOutput('(Please wait)')
71 playlist = self.GetActivePlaylist()
72 curves = playlist.curves
77 notflat = self.has_features(curve)
81 feature_string = str(notflat) + ' features'
83 feature_string = 'no features'
85 feature_string = '1 feature'
86 output_string = ''.join(['Curve ', curve.name, '(', str(curve_index), '/', str(len(curves)), '): ', feature_string])
89 output_string = ''.join(['Curve ', curve.name, '(', str(curve_index), '/', str(len(curves)), '): cannot be filtered. Probably unable to retrieve force data from corrupt file.'])
90 self.AppendToOutput(output_string)
92 curve.features = notflat
93 features.append(curve_index - 1)
95 self.AppendToOutput('Found nothing interesting. Check the playlist, could be a bug or criteria could be too stringent.')
97 if len(features) < playlist.count:
98 self.AppendToOutput(''.join(['Found ', str(len(features)), ' potentially interesting curves.']))
99 self.AppendToOutput('Regenerating playlist...')
100 playlist_filtered = playlist.filter_curves(features)
101 self.AddPlaylist(playlist_filtered, name='flatfilt')
103 self.AppendToOutput('No curves filtered. Try different filtering criteria.')
105 def has_features(self, curve):
107 decides if a curve is flat enough to be rejected from analysis: it sees if there
108 are at least min_npks points that are higher than min_deviation times the absolute value
111 Algorithm original idea by Francesco Musiani, with my tweaks and corrections.
114 mindeviation = self.GetIntFromConfig('flatfilts', 'flatfilt', 'min_deviation')
115 minpeaks = self.GetIntFromConfig('flatfilts', 'flatfilt', 'min_npks')
116 #medianfilter = self.GetIntFromConfig('flatfilt', 'median_filter')
117 #mindeviation = self.GetIntFromConfig('convfilt', 'mindeviation')
118 #minpeaks = self.GetIntFromConfig('convfilt', 'minpeaks')
122 #item.identify(self.drivers)
123 #we assume the first is the plot with the force curve
124 #do the median to better resolve features from noise
125 flat_plot = self.plotmanip_median(curve.driver.default_plots()[0], curve, customvalue=medianfilter)
126 flat_vects = flat_plot.vectors
127 curve.driver.close_all()
128 #needed to avoid *big* memory leaks!
132 #absolute value of derivate
133 yretdiff=diff(flat_vects[1][1])
134 yretdiff=[abs(value) for value in yretdiff]
135 #average of derivate values
136 diffmean=numpy.mean(yretdiff)
140 for value in yretdiff:
141 if value/diffmean > mindeviation:
146 if c_pks >= minpeaks:
149 del flat_plot, flat_vects, yretdiff
153 ################################################################
154 #-----CONVFILT-------------------------------------------------
155 #-----Convolution-based peak recognition and filtering.
156 #Requires the libpeakspot.py library
158 def has_peaks(self, plot, curve=None):
160 Finds peak position in a force curve.
161 FIXME: should be moved to libpeakspot.py
164 blindwindow = self.GetFloatFromConfig('flatfilts', 'convfilt', 'blindwindow')
165 #need to convert the string that contains the list into a list
166 convolution = eval(self.GetStringFromConfig('flatfilts', 'convfilt', 'convolution'))
167 maxcut = self.GetFloatFromConfig('flatfilts', 'convfilt', 'maxcut')
168 mindeviation = self.GetFloatFromConfig('flatfilts', 'convfilt', 'mindeviation')
169 positive = self.GetBoolFromConfig('flatfilts', 'convfilt', 'positive')
170 seedouble = self.GetIntFromConfig('flatfilts', 'convfilt', 'seedouble')
171 stable = self.GetFloatFromConfig('flatfilts', 'convfilt', 'stable')
173 xret = plot.vectors[1][0]
174 yret = plot.vectors[1][1]
175 #Calculate convolution
176 convoluted = lps.conv_dx(yret, convolution)
178 #surely cut everything before the contact point
179 cut_index = self.find_contact_point(plot, curve)
180 #cut even more, before the blind window
181 start_x = xret[cut_index]
183 for value in xret[cut_index:]:
184 if abs((value) - (start_x)) > blindwindow * (10 ** -9):
187 cut_index += blind_index
188 #do the dirty convolution-peak finding stuff
189 noise_level = lps.noise_absdev(convoluted[cut_index:], positive, maxcut, stable)
190 above = lps.abovenoise(convoluted, noise_level, cut_index, mindeviation)
191 peak_location, peak_size = lps.find_peaks(above, seedouble=seedouble)
193 for i in range(len(peak_location)):
194 peak = peak_location[i]
195 maxpk = min(yret[peak - 10:peak + 10])
196 index_maxpk = yret[peak - 10:peak + 10].index(maxpk) + (peak - 10)
197 peak_location[i] = index_maxpk
199 return peak_location, peak_size
201 def exec_has_peaks(self, curve):
203 encapsulates has_peaks for the purpose of correctly treating the curve objects in the convfilt loop,
204 to avoid memory leaks
206 #item.identify(self.drivers)
207 #we assume the first is the plot with the force curve
208 plot = curve.driver.default_plots()[0]
210 if self.HasPlotmanipulator('plotmanip_flatten'):
211 #If flatten is present, use it for better recognition of peaks...
212 #flatten = self._find_plotmanip('flatten') #extract flatten plot manipulator
213 #plot = flatten(plot, item, customvalue=1)
214 plot = self.plotmanip_flatten(plot, curve, customvalue=1)
216 peak_location, peak_size = self.has_peaks(plot, curve)
217 #close all open files
218 curve.driver.close_all()
219 #needed to avoid *big* memory leaks!
222 return peak_location, peak_size
224 #------------------------
225 #------commands----------
226 #------------------------
231 Test command for convolution filter / test.
233 Syntax: peaks [deviations]
234 absolute deviation = number of times the convolution signal is above the noise absolute deviation.
238 #TODO: check if the following line gives us what we need
239 curve = self.GetActiveCurve()
240 defplots = curve.driver.default_plots()[0] #we need the raw, uncorrected plots
242 #if 'flatten' in self.config['plotmanips']:
243 if self.HasPlotmanipulator('plotmanip_flatten'):
244 #flatten=self._find_plotmanip('flatten') #extract flatten plot manipulator
245 #defplots=flatten(defplots, self.current)
246 defplots = self.plotmanip_flatten(defplots, curve, customvalue=0)
248 self.AppendToOutput('The flatten plot manipulator is not loaded. Enabling it could give better results.')
250 peak_location, peak_size = self.has_peaks(defplots, curve)
251 self.AppendToOutput('Found ' + str(len(peak_location)) + ' peaks.')
252 self.AppendToOutput('peaks ' + curve.filename + ' ' + str(len(peak_location)))
253 #to_dump = 'peaks ' + current_curve.filename + ' ' + str(len(peak_location))
254 #self.outlet.push(to_dump)
256 #if no peaks, we have nothing to plot. exit.
258 #otherwise, we plot the peak locations.
259 xplotted_ret = curve.plots[0].vectors[1][0]
260 yplotted_ret = curve.plots[0].vectors[1][1]
261 xgood = [xplotted_ret[index] for index in peak_location]
262 ygood = [yplotted_ret[index] for index in peak_location]
264 curve.plots[0].add_set(xgood, ygood)
265 curve.plots[0].styles.append('scatter')
266 curve.plots[0].colors.append('indigo')
268 #recplot = self._get_displayed_plot()
269 #recplot.vectors.append([xgood,ygood])
270 #if recplot.styles == []:
271 #recplot.styles = [None, None, 'scatter']
272 #recplot.colors = [None, None, None]
274 #recplot.styles += ['scatter']
275 #recplot.colors += [None]
277 #self._send_plot([recplot])
280 def do_convfilt(self):
284 Filters out flat (featureless) curves of the current playlist,
285 creating a playlist containing only the curves with potential
289 convfilt [min_npks min_deviation]
291 min_npks = minmum number of peaks
292 (to set the default, see convfilt.conf file; CONVCONF and SETCONF commands)
294 min_deviation = minimum signal/noise ratio *in the convolution*
295 (to set the default, see convfilt.conf file; CONVCONF and SETCONF commands)
297 If called without arguments, it uses default values.
300 self.AppendToOutput('Processing playlist...')
301 self.AppendToOutput('(Please wait)')
302 minpeaks = self.GetIntFromConfig('flatfilts', 'convfilt', 'minpeaks')
304 playlist = self.GetActivePlaylist()
306 curves = self.GetActivePlaylist().curves
311 peak_location, peak_size = self.exec_has_peaks(curve)
312 number_of_peaks = len(peak_location)
313 if number_of_peaks != 1:
314 if number_of_peaks > 0:
315 feature_string = str(number_of_peaks) + ' features'
317 feature_string = 'no features'
319 feature_string = '1 feature'
320 if number_of_peaks >= minpeaks:
321 feature_string += '+'
322 output_string = ''.join(['Curve ', curve.name, '(', str(curve_index), '/', str(len(curves)), '): ', feature_string])
326 output_string = ''.join(['Curve ', curve.name, '(', str(curve_index), '/', str(len(curves)), '): cannot be filtered. Probably unable to retrieve force data from corrupt file.'])
327 self.AppendToOutput(output_string)
328 if number_of_peaks >= minpeaks:
329 curve.peak_location = peak_location
330 curve.peak_size = peak_size
331 features.append(curve_index - 1)
333 #TODO: do we need this? Flattening might not be necessary/desired
334 #Warn that no flattening had been done.
335 if not self.HasPlotmanipulator('plotmanip_flatten'):
336 self.AppendToOutput('Flatten manipulator was not found. Processing was done without flattening.')
337 self.AppendToOutput('Try to enable it in the configuration file for better results.')
339 self.AppendToOutput('Found nothing interesting. Check the playlist, could be a bug or criteria could be too stringent.')
341 if len(features) < playlist.count:
342 self.AppendToOutput(''.join(['Found ', str(len(features)), ' potentially interesting curves.']))
343 self.AppendToOutput('Regenerating playlist...')
344 playlist_filtered = playlist.filter_curves(features)
345 self.AddPlaylist(playlist_filtered, name='convfilt')
347 self.AppendToOutput('No curves filtered. Try different filtering criteria.')