4 Force spectroscopy curves filtering of flat curves
5 Licensed under the GNU LGPL version 2
7 Other plugin dependencies:
8 procplots.py (plot processing plugin)
11 from hooke.libhooke import WX_GOOD
14 wxversion.select(WX_GOOD)
15 import xml.dom.minidom
19 from numpy import diff
22 from .. import libpeakspot as lps
23 from .. import libhookecurve as lhc
26 class flatfiltsCommands(object):
29 #configurate convfilt variables
30 convfilt_configurator=ConvfiltConfig()
31 self.convfilt_config=convfilt_configurator.load_config('convfilt.conf')
33 def do_flatfilt(self,args):
37 Filters out flat (featureless) curves of the current playlist,
38 creating a playlist containing only the curves with potential
42 flatfilt [min_npks min_deviation]
44 min_npks = minmum number of points over the deviation
47 min_deviation = minimum signal/noise ratio
50 If called without arguments, it uses default values, that
51 should work most of the times.
60 min_deviation=int(args[1])
64 print 'Processing playlist...'
68 for item in self.current_list:
72 notflat=self.has_features(item, median_filter, min_npks, min_deviation)
73 print 'Curve',item.path, 'is',c,'of',len(self.current_list),': features are ',notflat
76 print 'Curve',item.path, 'is',c,'of',len(self.current_list),': cannot be filtered. Probably unable to retrieve force data from corrupt file.'
80 item.curve=None #empty the item object, to further avoid memory leak
81 notflat_list.append(item)
83 if len(notflat_list)==0:
84 print 'Found nothing interesting. Check your playlist, could be a bug or criteria could be too much stringent'
87 print 'Found ',len(notflat_list),' potentially interesting curves'
88 print 'Regenerating playlist...'
90 self.current_list=notflat_list
91 self.current=self.current_list[self.pointer]
94 def has_features(self,item,median_filter,min_npks,min_deviation):
96 decides if a curve is flat enough to be rejected from analysis: it sees if there
97 are at least min_npks points that are higher than min_deviation times the absolute value
100 Algorithm original idea by Francesco Musiani, with my tweaks and corrections.
104 item.identify(self.drivers)
105 #we assume the first is the plot with the force curve
106 #do the median to better resolve features from noise
107 flat_plot=self.plotmanip_median(item.curve.default_plots()[0], item, customvalue=median_filter)
108 flat_vects=flat_plot.vectors
109 item.curve.close_all()
110 #needed to avoid *big* memory leaks!
114 #absolute value of derivate
115 yretdiff=diff(flat_vects[1][1])
116 yretdiff=[abs(value) for value in yretdiff]
117 #average of derivate values
118 diffmean=numpy.mean(yretdiff)
122 for value in yretdiff:
123 if value/diffmean > min_deviation:
131 del flat_plot, flat_vects, yretdiff
135 ################################################################
136 #-----CONVFILT-------------------------------------------------
137 #-----Convolution-based peak recognition and filtering.
138 #Requires the libpeakspot.py library
140 def has_peaks(self, plot, abs_devs=None):
142 Finds peak position in a force curve.
143 FIXME: should be moved in libpeakspot.py
146 abs_devs=self.convfilt_config['mindeviation']
149 xret=plot.vectors[1][0]
150 yret=plot.vectors[1][1]
151 #Calculate convolution.
152 convoluted=lps.conv_dx(yret, self.convfilt_config['convolution'])
154 #surely cut everything before the contact point
155 cut_index=self.find_contact_point(plot)
156 #cut even more, before the blind window
157 start_x=xret[cut_index]
159 for value in xret[cut_index:]:
160 if abs((value) - (start_x)) > self.convfilt_config['blindwindow']*(10**-9):
163 cut_index+=blind_index
164 #do the dirty convolution-peak finding stuff
165 noise_level=lps.noise_absdev(convoluted[cut_index:], self.convfilt_config['positive'], self.convfilt_config['maxcut'], self.convfilt_config['stable'])
166 above=lps.abovenoise(convoluted,noise_level,cut_index,abs_devs)
167 peak_location,peak_size=lps.find_peaks(above,seedouble=self.convfilt_config['seedouble'])
170 for i in range(len(peak_location)):
171 peak=peak_location[i]
172 maxpk=min(yret[peak-10:peak+10])
173 index_maxpk=yret[peak-10:peak+10].index(maxpk)+(peak-10)
174 peak_location[i]=index_maxpk
176 return peak_location,peak_size
179 def exec_has_peaks(self,item,abs_devs):
181 encapsulates has_peaks for the purpose of correctly treating the curve objects in the convfilt loop,
182 to avoid memory leaks
184 item.identify(self.drivers)
185 #we assume the first is the plot with the force curve
186 plot=item.curve.default_plots()[0]
188 if 'flatten' in self.config['plotmanips']:
189 #If flatten is present, use it for better recognition of peaks...
190 flatten=self._find_plotmanip('flatten') #extract flatten plot manipulator
191 plot=flatten(plot, item, customvalue=1)
193 peak_location,peak_size=self.has_peaks(plot,abs_devs)
194 #close all open files
195 item.curve.close_all()
196 #needed to avoid *big* memory leaks!
199 return peak_location, peak_size
201 #------------------------
202 #------commands----------
203 #------------------------
204 def do_peaks(self,args):
208 Test command for convolution filter / test.
210 Syntax: peaks [deviations]
211 absolute deviation = number of times the convolution signal is above the noise absolute deviation.
215 args=self.convfilt_config['mindeviation']
220 print 'Wrong argument, using config value'
221 abs_devs=float(self.convfilt_config['mindeviation'])
223 defplots=self.current.curve.default_plots()[0] #we need the raw, uncorrected plots
225 if 'flatten' in self.config['plotmanips']:
226 flatten=self._find_plotmanip('flatten') #extract flatten plot manipulator
227 defplots=flatten(defplots, self.current)
229 print 'You have the flatten plot manipulator not loaded. Enabling it could give you better results.'
231 peak_location,peak_size=self.has_peaks(defplots,abs_devs)
232 print 'Found '+str(len(peak_location))+' peaks.'
233 to_dump='peaks '+self.current.path+' '+str(len(peak_location))
234 self.outlet.push(to_dump)
237 #if no peaks, we have nothing to plot. exit.
238 if len(peak_location)==0:
241 #otherwise, we plot the peak locations.
242 xplotted_ret=self.plots[0].vectors[1][0]
243 yplotted_ret=self.plots[0].vectors[1][1]
244 xgood=[xplotted_ret[index] for index in peak_location]
245 ygood=[yplotted_ret[index] for index in peak_location]
247 recplot=self._get_displayed_plot()
248 recplot.vectors.append([xgood,ygood])
249 if recplot.styles==[]:
250 recplot.styles=[None,None,'scatter']
251 recplot.colors=[None,None,None]
253 recplot.styles+=['scatter']
254 recplot.colors+=[None]
256 self._send_plot([recplot])
258 def do_convfilt(self,args):
262 Filters out flat (featureless) curves of the current playlist,
263 creating a playlist containing only the curves with potential
267 convfilt [min_npks min_deviation]
269 min_npks = minmum number of peaks
270 (to set the default, see convfilt.conf file; CONVCONF and SETCONF commands)
272 min_deviation = minimum signal/noise ratio *in the convolution*
273 (to set the default, see convfilt.conf file; CONVCONF and SETCONF commands)
275 If called without arguments, it uses default values.
278 min_npks=self.convfilt_config['minpeaks']
279 min_deviation=self.convfilt_config['mindeviation']
283 min_npks=int(args[0])
284 min_deviation=int(args[1])
288 print 'Processing playlist...'
289 print '(Please wait)'
293 for item in self.current_list:
297 peak_location,peak_size=self.exec_has_peaks(item,min_deviation)
298 if len(peak_location)>=min_npks:
302 print 'Curve',item.path, 'is',c,'of',len(self.current_list),': found '+str(len(peak_location))+' peaks.'+isok
304 peak_location,peak_size=[],[]
305 print 'Curve',item.path, 'is',c,'of',len(self.current_list),': cannot be filtered. Probably unable to retrieve force data from corrupt file.'
307 if len(peak_location)>=min_npks:
308 item.peak_location=peak_location
309 item.peak_size=peak_size
310 item.curve=None #empty the item object, to further avoid memory leak
311 notflat_list.append(item)
313 #Warn that no flattening had been done.
314 if not ('flatten' in self.config['plotmanips']):
315 print 'Flatten manipulator was not found. Processing was done without flattening.'
316 print 'Try to enable it in your configuration file for better results.'
318 if len(notflat_list)==0:
319 print 'Found nothing interesting. Check your playlist, could be a bug or criteria could be too much stringent'
322 print 'Found ',len(notflat_list),' potentially interesting curves'
323 print 'Regenerating playlist...'
325 self.current_list=notflat_list
326 self.current=self.current_list[self.pointer]
330 def do_setconv(self,args):
334 Sets the convfilt configuration variables
336 Syntax: setconv variable value
339 #FIXME: a general "set dictionary" function has to be built
341 print self.convfilt_config
343 if not (args[0] in self.convfilt_config.keys()):
344 print 'This is not an internal convfilt variable!'
345 print 'Run "setconv" without arguments to see a list of defined variables.'
349 print self.convfilt_config[args[0]]
352 self.convfilt_config[args[0]]=eval(args[1])
353 except NameError: #we have a string argument
354 self.convfilt_config[args[0]]=args[1]
357 #########################
358 #HANDLING OF CONFIGURATION FILE
359 class ConvfiltConfig(object):
361 Handling of convfilt configuration file
363 Mostly based on the simple-yet-useful examples of the Python Library Reference
364 about xml.dom.minidom
366 FIXME: starting to look a mess, should require refactoring
373 def load_config(self, filename):
374 myconfig=file(filename)
375 #the following 3 lines are needed to strip newlines. otherwise, since newlines
376 #are XML elements too, the parser would read them (and re-save them, multiplying
378 #yes, I'm an XML n00b
379 the_file=myconfig.read()
380 the_file_lines=the_file.split('\n')
381 the_file=''.join(the_file_lines)
383 self.config_tree=xml.dom.minidom.parseString(the_file)
385 def getText(nodelist):
386 #take the text from a nodelist
387 #from Python Library Reference 13.7.2
389 for node in nodelist:
390 if node.nodeType == node.TEXT_NODE:
394 def handleConfig(config):
395 noiseabsdev_elements=config.getElementsByTagName("noise_absdev")
396 convfilt_elements=config.getElementsByTagName("convfilt")
397 handleAbsdev(noiseabsdev_elements)
398 handleConvfilt(convfilt_elements)
400 def handleAbsdev(noiseabsdev_elements):
401 for element in noiseabsdev_elements:
402 for attribute in element.attributes.keys():
403 self.config[attribute]=element.getAttribute(attribute)
405 def handleConvfilt(convfilt_elements):
406 for element in convfilt_elements:
407 for attribute in element.attributes.keys():
408 self.config[attribute]=element.getAttribute(attribute)
410 handleConfig(self.config_tree)
411 #making items in the dictionary machine-readable
412 for item in self.config.keys():
414 self.config[item]=eval(self.config[item])
415 except NameError: #if it's an unreadable string, keep it as a string