6 Force spectroscopy curves basic fitting plugin.
7 Licensed under the GNU GPL version 2
9 Non-standard Dependencies:
10 procplots.py (plot processing plugin)
12 from libhooke import WX_GOOD, ClickedPoint
14 wxversion.select(WX_GOOD)
15 #from wx import PostEvent
16 #from wx.lib.newevent import NewEvent
23 global EVT_MEASURE_WLC
25 #measure_wlc, EVT_MEASURE_WLC = NewEvent()
27 global events_from_fit
28 events_from_fit=Queue.Queue() #GUI ---> CLI COMMUNICATION
35 self.wlccontact_point=None
36 self.wlccontact_index=None
38 def wlc_fit(self,clicked_points,xvector,yvector, pl_value, T=293):
40 Worm-like chain model fitting.
41 The function is the simple polynomial worm-like chain as proposed by C.Bustamante, J.F.Marko, E.D.Siggia
42 and S.Smith (Science. 1994 Sep 9;265(5178):1599-600.)
45 '''clicked_points[0] = contact point (calculated or hand-clicked)
46 clicked_points[1] and [2] are edges of chunk'''
48 #STEP 1: Prepare the vectors to apply the fit.
50 #indexes of the selected chunk
51 first_index=min(clicked_points[1].index, clicked_points[2].index)
52 last_index=max(clicked_points[1].index, clicked_points[2].index)
54 #getting the chunk and reverting it
55 xchunk,ychunk=xvector[first_index:last_index],yvector[first_index:last_index]
58 #put contact point at zero and flip around the contact point (the fit wants a positive growth for extension and force)
59 xchunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xchunk]
60 ychunk_corr_up=[-(y-clicked_points[0].graph_coords[1]) for y in ychunk]
62 xchunk_corr_up=scipy.array(xchunk_corr_up)
63 ychunk_corr_up=scipy.array(ychunk_corr_up)
66 #STEP 2: actually do the fit
68 #Find furthest point of chunk and add it a bit; the fit must converge
70 xchunk_high=max(xchunk_corr_up)
71 xchunk_high+=(xchunk_high/10)
73 #Here are the linearized start parameters for the WLC.
74 #[lambd=1/Lo , pii=1/P]
76 p0=[(1/xchunk_high),(1/(3.5e-10))]
77 p0_plfix=[(1/xchunk_high)]
79 def residuals(params,y,x,T):
81 Calculates the residuals of the fit
89 err = y-( (therm*pii/4) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd)) )
93 def wlc_eval(x,params,pl_value,T):
95 Evaluates the WLC function
105 Kb=(1.38065e-23) #boltzmann constant
106 #T=293 #temperature FIXME:should be user-modifiable!
107 therm=Kb*T #so we have thermal energy
109 return ( (therm*pii/4.0) * (((1-(x*lambd))**-2.0) - 1 + (4.0*x*lambd)) )
111 def residuals_plfix(params, y, x, pii, T):
113 Calculates the residuals of the fit, if we have the persistent length from an external source
120 err = y-( (therm*pii/4) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd)) )
124 #make the fit! and obtain params
126 plsq=scipy.optimize.leastsq(residuals_plfix, p0_plfix, args=(ychunk_corr_up,xchunk_corr_up,1/pl_value,T))
128 plsq=scipy.optimize.leastsq(residuals, p0, args=(ychunk_corr_up,xchunk_corr_up,T))
131 #STEP 3: plotting the fit
133 #obtain domain to plot the fit - from contact point to last_index plus 20 points
134 thule_index=last_index+10
135 if thule_index > len(xvector): #for rare cases in which we fit something at the END of whole curve.
136 thule_index = len(xvector)
137 #reverse etc. the domain
138 xfit_chunk=xvector[clicked_points[0].index:thule_index]
140 xfit_chunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xfit_chunk]
141 xfit_chunk_corr_up=scipy.array(xfit_chunk_corr_up)
143 #the fitted curve: reflip, re-uncorrect
144 yfit=wlc_eval(xfit_chunk_corr_up, plsq[0],pl_value,T)
145 yfit_down=[-y for y in yfit]
146 yfit_corr_down=[y+clicked_points[0].graph_coords[1] for y in yfit_down]
148 #get out true fit paramers
151 fit_out=[(1.0/x) for x in fit_out]
152 except TypeError: #if we fit only 1 parameter, we have a float and not a list in output.
153 fit_out=[(1.0/fit_out)]
155 return fit_out, yfit_corr_down, xfit_chunk
158 def do_wlc(self,args):
162 Fits a worm-like chain entropic rise to a given chunk of the curve.
164 First you have to click a contact point.
165 Then you have to click the two edges of the data you want to fit.
166 The function is the simple polynomial worm-like chain as proposed by
167 C.Bustamante, J.F.Marko, E.D.Siggia and S.Smith (Science. 1994
168 Sep 9;265(5178):1599-600.)
171 pl=[value] : Use a fixed persistent length for the fit. If pl is not given,
172 the fit will be a 2-variable
173 fit. DO NOT put spaces between 'pl', '=' and the value.
174 The value must be in meters.
175 Scientific notation like 0.35e-9 is fine.
177 t=[value] : Use a user-defined temperature. The value must be in
178 kelvins; by default it is 293 K.
179 DO NOT put spaces between 't', '=' and the value.
181 noauto : allows for clicking the contact point by
182 hand (otherwise it is automatically estimated) the first time.
183 If subsequent measurements are made, the same contact point
186 reclick : redefines by hand the contact point, if noauto has been used before
187 but the user is unsatisfied of the previously choosen contact point.
189 Syntax: wlc [pl=(value)] [t=value] [noauto]
192 T=self.config['temperature']
193 for arg in args.split():
194 #look for a persistent length argument.
196 pl_expression=arg.split('=')
197 pl_value=float(pl_expression[1]) #actual value
198 #look for a T argument. FIXME: spaces are not allowed between 'pl' and value
199 if ('t=' in arg[0:2]) or ('T=' in arg[0:2]):
200 t_expression=arg.split('=')
201 T=float(t_expression[1])
203 #use the currently displayed plot for the fit
204 displayed_plot=self._get_displayed_plot()
206 #handle contact point arguments correctly
207 if 'reclick' in args.split():
208 print 'Click contact point'
209 contact_point=self._measure_N_points(N=1, whatset=1)[0]
210 contact_point_index=contact_point.index
211 self.wlccontact_point=contact_point
212 self.wlccontact_index=contact_point.index
213 self.wlccurrent=self.current.path
214 elif 'noauto' in args.split():
215 if self.wlccontact_index==None or self.wlccurrent != self.current.path:
216 print 'Click contact point'
217 contact_point=self._measure_N_points(N=1, whatset=1)[0]
218 contact_point_index=contact_point.index
219 self.wlccontact_point=contact_point
220 self.wlccontact_index=contact_point.index
221 self.wlccurrent=self.current.path
223 contact_point=self.wlccontact_point
224 contact_point_index=self.wlccontact_index
226 cindex=self.find_contact_point()
227 contact_point=ClickedPoint()
228 contact_point.absolute_coords=displayed_plot.vectors[1][0][cindex], displayed_plot.vectors[1][1][cindex]
229 contact_point.find_graph_coords(displayed_plot.vectors[1][0], displayed_plot.vectors[1][1])
230 contact_point.is_marker=True
232 print 'Click edges of chunk'
233 points=self._measure_N_points(N=2, whatset=1)
234 points=[contact_point]+points
236 params, yfit, xfit = self.wlc_fit(points, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1],pl_value,T)
238 print 'Fit not possible. Probably wrong interval -did you click two *different* points?'
241 print 'Contour length: ',params[0]*(1.0e+9),' nm'
242 to_dump='contour '+self.current.path+' '+str(params[0]*(1.0e+9))+' nm'
243 self.outlet.push(to_dump)
244 if len(params)==2: #if we did choose 2-value fit
245 print 'Persistent length: ',params[1]*(1.0e+9),' nm'
246 to_dump='persistent '+self.current.path+' '+str(params[1]*(1.0e+9))+' nm'
247 self.outlet.push(to_dump)
249 #add the clicked points in the final PlotObject
250 clickvector_x, clickvector_y=[], []
252 clickvector_x.append(item.graph_coords[0])
253 clickvector_y.append(item.graph_coords[1])
255 #create a custom PlotObject to gracefully plot the fit along the curves
257 fitplot=copy.deepcopy(displayed_plot)
258 fitplot.add_set(xfit,yfit)
259 fitplot.add_set(clickvector_x,clickvector_y)
261 if fitplot.styles==[]:
262 fitplot.styles=[None,None,None,'scatter']
264 fitplot.styles+=[None,'scatter']
266 self._send_plot([fitplot])
268 def find_contact_point(self):
270 Finds the contact point on the curve.
272 The current algorithm (thanks to Francesco Musiani, francesco.musiani@unibo.it and Massimo Sandal) is:
273 - take care of the PicoForce trigger bug - exclude retraction portions with too high standard deviation
274 - fit the second half of the retraction curve to a line
275 - if the fit is not almost horizontal, take a smaller chunk and repeat
276 - otherwise, we have something horizontal
277 - so take the average of horizontal points and use it as a baseline
279 Then, start from the rise of the retraction curve and look at the first point below the
282 FIXME: should be moved, probably to generalvclamp.py
284 outplot=self.subtract_curves(1)
285 xret=outplot.vectors[1][0]
286 ydiff=outplot.vectors[1][1]
288 xext=self.plots[0].vectors[0][0]
289 yext=self.plots[0].vectors[0][1]
290 xret2=self.plots[0].vectors[1][0]
291 yret=self.plots[0].vectors[1][1]
293 #taking care of the picoforce trigger bug: we exclude portions of the curve that have too much
294 #standard deviation. yes, a lot of magic is here.
296 monlength=len(xret)-int(len(xret)/20)
299 monchunk=scipy.array(ydiff[monlength:finalength])
300 if abs(scipy.stats.std(monchunk)) < 2e-10:
302 else: #move away from the monster
303 monlength-=int(len(xret)/50)
304 finalength-=int(len(xret)/50)
307 #take half of the thing
308 endlength=int(len(xret)/2)
313 xchunk=yext[endlength:monlength]
314 ychunk=yext[endlength:monlength]
315 regr=scipy.stats.linregress(xchunk,ychunk)[0:2]
316 #we stop if we found an almost-horizontal fit or if we're going too short...
317 #FIXME: 0.1 and 6 here are "magic numbers" (although reasonable)
318 if (abs(regr[1]) > 0.1) and ( endlength < len(xret)-int(len(xret)/6) ) :
324 ymean=scipy.mean(ychunk) #baseline
329 #find the first point below the calculated baseline
335 #The algorithm didn't find anything below the baseline! It should NEVER happen
343 def find_contact_point2(self, debug=False):
345 TO BE DEVELOPED IN THE FUTURE
346 Finds the contact point on the curve.
348 FIXME: should be moved, probably to generalvclamp.py
350 outplot=self.subtract_curves(1)
351 xret=outplot.vectors[1][0]
352 ydiff=outplot.vectors[1][1]
354 raw_plot=self.current.curve.default_plots()[0]
356 '''xext=self.plots[0].vectors[0][0]
357 yext=self.plots[0].vectors[0][1]
358 xret2=self.plots[0].vectors[1][0]
359 yret=self.plots[0].vectors[1][1]
361 xext=raw_plot.vectors[0][0]
362 yext=raw_plot.vectors[0][1]
363 xret2=raw_plot.vectors[1][0]
364 yret=raw_plot.vectors[1][1]
365 #taking care of the picoforce trigger bug: we exclude portions of the curve that have too much
366 #standard deviation. yes, a lot of magic is here.
369 #take half of the thing
370 endlength=int(len(xext)/2)
373 xchunk=xext[endlength:monlength]
374 ychunk=yext[endlength:monlength]
375 regr=scipy.polyfit(xchunk,ychunk,1)[0:2]
379 xchunk=yext[endlength:monlength]
380 ychunk=yext[endlength:monlength]
382 #regr=scipy.stats.linregress(xchunk,ychunk)[0:2]
383 regr=scipy.polyfit(xchunk,ychunk,1)[0:2]
384 #we stop if we found an almost-horizontal fit or if we're going too short...
385 #FIXME: 0.1 and 6 here are "magic numbers" (although reasonable)
386 if (abs(regr[1]) > 0.1) and ( endlength < len(xret)-int(len(xret)/6) ) :
393 ymean=scipy.mean(ychunk) #baseline
398 #find the first point below the calculated baseline
404 #The algorithm didn't find anything below the baseline! It should NEVER happen
407 #fit the contact line
409 x_contact_chunk=xret2[0:n_contact]
410 y_contact_chunk=yret[0:n_contact]
412 regr_contact=scipy.polyfit(x_contact_chunk,y_contact_chunk,1)[0:2]
413 x_intercept=(regr_contact[1]-regr[1])/(regr[0]-regr_contact[0])
414 y_intercept=(regr_contact[0]*x_intercept + regr_contact[1])
416 #now, exploit a ClickedPoint instance to calculate index...
418 dummy.absolute_coords=(x_intercept,y_intercept)
419 dummy.find_graph_coords(xret2,yret)
422 return dummy.index, regr, regr_contact
427 def x_do_contact(self,args):
429 DEBUG COMMAND to be activated in the future
431 index,regr,regr_contact=self.find_contact_point2(debug=True)
434 raw_plot=self.current.curve.default_plots()[0]
435 xret=raw_plot.vectors[0][0]
436 #nc_line=[(item*regr[0])+regr[1] for item in x_nc]
437 nc_line=scipy.polyval(regr,xret)
438 c_line=scipy.polyval(regr_contact,xret)
441 contact_plot=self.current.curve.default_plots()[0]
442 contact_plot.add_set(xret, nc_line)
443 contact_plot.add_set(xret, c_line)
444 contact_plot.styles=[None,None,None,None]
445 #contact_plot.styles.append(None)
446 contact_plot.destination=1
447 self._send_plot([contact_plot])