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
24 global EVT_MEASURE_WLC
26 #measure_wlc, EVT_MEASURE_WLC = NewEvent()
28 global events_from_fit
29 events_from_fit=Queue.Queue() #GUI ---> CLI COMMUNICATION
36 self.wlccontact_point=None
37 self.wlccontact_index=None
39 def wlc_fit(self,clicked_points,xvector,yvector, pl_value, T=293, return_errors=False):
41 Worm-like chain model fitting.
42 The function is the simple polynomial worm-like chain as proposed by C.Bustamante, J.F.Marko, E.D.Siggia
43 and S.Smith (Science. 1994 Sep 9;265(5178):1599-600.)
46 '''clicked_points[0] = contact point (calculated or hand-clicked)
47 clicked_points[1] and [2] are edges of chunk'''
49 #STEP 1: Prepare the vectors to apply the fit.
51 if pl_value is not None:
52 pl_value=pl_value/(10**9)
54 #indexes of the selected chunk
55 first_index=min(clicked_points[1].index, clicked_points[2].index)
56 last_index=max(clicked_points[1].index, clicked_points[2].index)
58 #getting the chunk and reverting it
59 xchunk,ychunk=xvector[first_index:last_index],yvector[first_index:last_index]
62 #put contact point at zero and flip around the contact point (the fit wants a positive growth for extension and force)
63 xchunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xchunk]
64 ychunk_corr_up=[-(y-clicked_points[0].graph_coords[1]) for y in ychunk]
67 xchunk_corr_up=scipy.array(xchunk_corr_up)
68 ychunk_corr_up=scipy.array(ychunk_corr_up)
71 #STEP 2: actually do the fit
73 #Find furthest point of chunk and add it a bit; the fit must converge
75 xchunk_high=max(xchunk_corr_up)
76 xchunk_high+=(xchunk_high/10)
78 #Here are the linearized start parameters for the WLC.
79 #[lambd=1/Lo , pii=1/P]
81 p0=[(1/xchunk_high),(1/(3.5e-10))]
82 p0_plfix=[(1/xchunk_high)]
85 fixme: remove these comments after testing
89 def f_wlc(params,x,T=T):
91 wlc function for ODR fitting
96 y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd))
99 def f_wlc_plfix(params,x,pl_value=pl_value,T=T):
101 wlc function for ODR fitting
107 y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd))
111 realdata=scipy.odr.RealData(xchunk_corr_up,ychunk_corr_up)
113 model=scipy.odr.Model(f_wlc_plfix)
114 o = scipy.odr.ODR(realdata, model, p0_plfix)
116 model=scipy.odr.Model(f_wlc)
117 o = scipy.odr.ODR(realdata, model, p0)
119 o.set_job(fit_type=2)
121 fit_out=[(1/i) for i in out.beta]
123 #Calculate fit errors from output standard deviations.
124 #We must propagate the error because we fit the *inverse* parameters!
125 #The error = (error of the inverse)*(value**2)
127 for sd,value in zip(out.sd_beta, fit_out):
128 err_real=sd*(value**2)
129 fit_errors.append(err_real)
131 def wlc_eval(x,params,pl_value,T):
133 Evaluates the WLC function
143 Kb=(1.38065e-23) #boltzmann constant
144 therm=Kb*T #so we have thermal energy
146 return ( (therm*pii/4.0) * (((1-(x*lambd))**-2.0) - 1 + (4.0*x*lambd)) )
148 #STEP 3: plotting the fit
150 #obtain domain to plot the fit - from contact point to last_index plus 20 points
151 thule_index=last_index+10
152 if thule_index > len(xvector): #for rare cases in which we fit something at the END of whole curve.
153 thule_index = len(xvector)
154 #reverse etc. the domain
155 xfit_chunk=xvector[clicked_points[0].index:thule_index]
157 xfit_chunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xfit_chunk]
158 xfit_chunk_corr_up=scipy.array(xfit_chunk_corr_up)
160 #the fitted curve: reflip, re-uncorrect
161 yfit=wlc_eval(xfit_chunk_corr_up, out.beta, pl_value,T)
162 yfit_down=[-y for y in yfit]
163 yfit_corr_down=[y+clicked_points[0].graph_coords[1] for y in yfit_down]
166 return fit_out, yfit_corr_down, xfit_chunk, fit_errors
168 return fit_out, yfit_corr_down, xfit_chunk, None
170 def fjc_fit(self,clicked_points,xvector,yvector, pl_value, T=293, return_errors=False):
172 Freely-jointed chain function
173 ref: C.Ray and B.B. Akhremitchev;h ttp://www.chem.duke.edu/~boris/research/force_spectroscopy/fit_efjc.pdf
176 '''clicked_points[0] = contact point (calculated or hand-clicked)
177 clicked_points[1] and [2] are edges of chunk'''
179 #STEP 1: Prepare the vectors to apply the fit.
180 if pl_value is not None:
181 pl_value=pl_value/(10**9)
183 #indexes of the selected chunk
184 first_index=min(clicked_points[1].index, clicked_points[2].index)
185 last_index=max(clicked_points[1].index, clicked_points[2].index)
187 #getting the chunk and reverting it
188 xchunk,ychunk=xvector[first_index:last_index],yvector[first_index:last_index]
191 #put contact point at zero and flip around the contact point (the fit wants a positive growth for extension and force)
192 xchunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xchunk]
193 ychunk_corr_up=[-(y-clicked_points[0].graph_coords[1]) for y in ychunk]
196 xchunk_corr_up=scipy.array(xchunk_corr_up)
197 ychunk_corr_up=scipy.array(ychunk_corr_up)
200 #STEP 2: actually do the fit
202 #Find furthest point of chunk and add it a bit; the fit must converge
204 xchunk_high=max(xchunk_corr_up)
205 xchunk_high+=(xchunk_high/10)
207 #Here are the linearized start parameters for the WLC.
208 #[lambd=1/Lo , pii=1/P]
210 p0=[(1/xchunk_high),(1/(3.5e-10))]
211 p0_plfix=[(1/xchunk_high)]
214 fixme: remove these comments after testing
220 return (np.exp(2*z)+1)/(np.exp(2*z)-1)
222 def x_fjc(params,f,T=T):
224 fjc function for ODR fitting
230 #x=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd))
231 x=(1/lambd)*(coth(f*(1/pii)/therm) - (therm*pii)/f)
234 def x_fjc_plfix(params,f,pl_value=pl_value,T=T):
236 fjc function for ODR fitting
242 #y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd))
243 x=(1/lambd)*(coth(f*(1/pii)/therm) - (therm*pii)/f)
247 realdata=scipy.odr.RealData(ychunk_corr_up,xchunk_corr_up)
249 model=scipy.odr.Model(x_fjc_plfix)
250 o = scipy.odr.ODR(realdata, model, p0_plfix)
252 model=scipy.odr.Model(x_fjc)
253 o = scipy.odr.ODR(realdata, model, p0)
255 o.set_job(fit_type=2)
257 fit_out=[(1/i) for i in out.beta]
259 #Calculate fit errors from output standard deviations.
260 #We must propagate the error because we fit the *inverse* parameters!
261 #The error = (error of the inverse)*(value**2)
263 for sd,value in zip(out.sd_beta, fit_out):
264 err_real=sd*(value**2)
265 fit_errors.append(err_real)
267 def fjc_eval(y,params,pl_value,T):
269 Evaluates the WLC function
279 Kb=(1.38065e-23) #boltzmann constant
280 therm=Kb*T #so we have thermal energy
281 #return ( (therm*pii/4.0) * (((1-(x*lambd))**-2.0) - 1 + (4.0*x*lambd)) )
282 return (1/lambd)*(coth(y*(1/pii)/therm) - (therm*pii)/y)
285 #STEP 3: plotting the fit
287 #obtain domain to plot the fit - from contact point to last_index plus 20 points
288 thule_index=last_index+10
289 if thule_index > len(xvector): #for rare cases in which we fit something at the END of whole curve.
290 thule_index = len(xvector)
291 #reverse etc. the domain
293 xfit_chunk=xvector[clicked_points[0].index:thule_index]
295 xfit_chunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xfit_chunk]
296 xfit_chunk_corr_up=scipy.array(xfit_chunk_corr_up)
298 #the fitted curve: reflip, re-uncorrect
299 yfit=wlc_eval(xfit_chunk_corr_up, out.beta, pl_value,T)
300 yfit_down=[-y for y in yfit]
301 yfit_corr_down=[y+clicked_points[0].graph_coords[1] for y in yfit_down]
303 #xfit_chunk=xvector[clicked_points[0].index:thule_index]
305 #yfit=wlc_eval(xfit_chunk_corr_up, out.beta, pl_value,T)
306 ychunk=yvector[clicked_points[0].index:thule_index]
307 yfit_down=[-y for y in ychunk]
308 yfit_corr_down=[y+clicked_points[0].graph_coords[1] for y in yfit_down]
309 yfit_corr_down=scipy.array(yfit_corr_down)
311 #the fitted curve: reflip, re-uncorrect
312 xfit=fjc_eval(yfit_corr_down, out.beta, pl_value,T)
315 xfit_chunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xfit]
316 #xfit_chunk_corr_up=scipy.array(xfit_chunk_corr_up)
320 #print yfit_chunk_corr_up
321 #print xfit_corr_down
324 return fit_out, yfit_corr_down, xfit_chunk_corr_up, fit_errors
326 return fit_out, yfit_corr_down, xfit_chunk_corr_up, None
329 def do_wlc(self,args):
333 Fits a worm-like chain entropic rise to a given chunk of the curve.
335 First you have to click a contact point.
336 Then you have to click the two edges of the data you want to fit.
337 The function is the simple polynomial worm-like chain as proposed by
338 C.Bustamante, J.F.Marko, E.D.Siggia and S.Smith (Science. 1994
339 Sep 9;265(5178):1599-600.)
342 pl=[value] : Use a fixed persistent length for the fit. If pl is not given,
343 the fit will be a 2-variable
344 fit. DO NOT put spaces between 'pl', '=' and the value.
345 The value must be in nanometers.
347 t=[value] : Use a user-defined temperature. The value must be in
348 kelvins; by default it is 293 K.
349 DO NOT put spaces between 't', '=' and the value.
351 noauto : allows for clicking the contact point by
352 hand (otherwise it is automatically estimated) the first time.
353 If subsequent measurements are made, the same contact point
356 reclick : redefines by hand the contact point, if noauto has been used before
357 but the user is unsatisfied of the previously choosen contact point.
359 Syntax: wlc [pl=(value)] [t=value] [noauto]
362 T=self.config['temperature']
363 for arg in args.split():
364 #look for a persistent length argument.
366 pl_expression=arg.split('=')
367 pl_value=float(pl_expression[1]) #actual value
368 #look for a T argument. FIXME: spaces are not allowed between 'pl' and value
369 if ('t=' in arg[0:2]) or ('T=' in arg[0:2]):
370 t_expression=arg.split('=')
371 T=float(t_expression[1])
373 #use the currently displayed plot for the fit
374 displayed_plot=self._get_displayed_plot()
376 #handle contact point arguments correctly
377 if 'reclick' in args.split():
378 print 'Click contact point'
379 contact_point=self._measure_N_points(N=1, whatset=1)[0]
380 contact_point_index=contact_point.index
381 self.wlccontact_point=contact_point
382 self.wlccontact_index=contact_point.index
383 self.wlccurrent=self.current.path
384 elif 'noauto' in args.split():
385 if self.wlccontact_index==None or self.wlccurrent != self.current.path:
386 print 'Click contact point'
387 contact_point=self._measure_N_points(N=1, whatset=1)[0]
388 contact_point_index=contact_point.index
389 self.wlccontact_point=contact_point
390 self.wlccontact_index=contact_point.index
391 self.wlccurrent=self.current.path
393 contact_point=self.wlccontact_point
394 contact_point_index=self.wlccontact_index
396 cindex=self.find_contact_point()
397 contact_point=ClickedPoint()
398 contact_point.absolute_coords=displayed_plot.vectors[1][0][cindex], displayed_plot.vectors[1][1][cindex]
399 contact_point.find_graph_coords(displayed_plot.vectors[1][0], displayed_plot.vectors[1][1])
400 contact_point.is_marker=True
402 print 'Click edges of chunk'
403 points=self._measure_N_points(N=2, whatset=1)
404 points=[contact_point]+points
406 if self.config['fit_function']=='wlc':
407 params, yfit, xfit, fit_errors = self.wlc_fit(points, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1],pl_value,T, return_errors=True )
408 elif self.config['fit_function']=='fjc':
409 params, yfit, xfit, fit_errors = self.fjc_fit(points, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1],pl_value,T, return_errors=True )
411 print 'No recognized fit function defined!'
412 print 'Set your fit function to wlc or fjc.'
416 print 'Fit not possible. Probably wrong interval -did you click two *different* points?'
419 #FIXME: print "Kuhn length" for FJC
420 print 'Fit function:',self.config['fit_function']
421 print 'Contour length: ',params[0]*(1.0e+9),' nm'
422 to_dump='contour '+self.current.path+' '+str(params[0]*(1.0e+9))+' nm'
423 self.outlet.push(to_dump)
424 if len(params)==2: #if we did choose 2-value fit
425 print 'Persistent length: ',params[1]*(1.0e+9),' nm'
426 to_dump='persistent '+self.current.path+' '+str(params[1]*(1.0e+9))+' nm'
427 self.outlet.push(to_dump)
430 fit_nm=[i*(10**9) for i in fit_errors]
431 print 'Standard deviation (contour length)', fit_nm[0]
433 print 'Standard deviation (persistent length)', fit_nm[1]
436 #add the clicked points in the final PlotObject
437 clickvector_x, clickvector_y=[], []
439 clickvector_x.append(item.graph_coords[0])
440 clickvector_y.append(item.graph_coords[1])
442 #create a custom PlotObject to gracefully plot the fit along the curves
444 fitplot=copy.deepcopy(displayed_plot)
445 fitplot.add_set(xfit,yfit)
446 fitplot.add_set(clickvector_x,clickvector_y)
448 #FIXME: this colour/styles stuff must be solved at the root!
449 if fitplot.styles==[]:
450 fitplot.styles=[None,None,None,'scatter']
452 fitplot.styles+=[None,'scatter']
454 if fitplot.colors==[]:
455 fitplot.colors=[None,None,None,None]
457 fitplot.colors+=[None,None]
459 self._send_plot([fitplot])
469 def find_contact_point(self,plot=False):
471 Finds the contact point on the curve.
473 The current algorithm (thanks to Francesco Musiani, francesco.musiani@unibo.it and Massimo Sandal) is:
474 - take care of the PicoForce trigger bug - exclude retraction portions with too high standard deviation
475 - fit the second half of the retraction curve to a line
476 - if the fit is not almost horizontal, take a smaller chunk and repeat
477 - otherwise, we have something horizontal
478 - so take the average of horizontal points and use it as a baseline
480 Then, start from the rise of the retraction curve and look at the first point below the
483 FIXME: should be moved, probably to generalvclamp.py
489 outplot=self.subtract_curves(1)
490 xret=outplot.vectors[1][0]
491 ydiff=outplot.vectors[1][1]
493 xext=plot.vectors[0][0]
494 yext=plot.vectors[0][1]
495 xret2=plot.vectors[1][0]
496 yret=plot.vectors[1][1]
498 #taking care of the picoforce trigger bug: we exclude portions of the curve that have too much
499 #standard deviation. yes, a lot of magic is here.
501 monlength=len(xret)-int(len(xret)/20)
504 monchunk=scipy.array(ydiff[monlength:finalength])
505 if abs(np.std(monchunk)) < 2e-10:
507 else: #move away from the monster
508 monlength-=int(len(xret)/50)
509 finalength-=int(len(xret)/50)
512 #take half of the thing
513 endlength=int(len(xret)/2)
518 xchunk=yext[endlength:monlength]
519 ychunk=yext[endlength:monlength]
520 regr=scipy.stats.linregress(xchunk,ychunk)[0:2]
521 #we stop if we found an almost-horizontal fit or if we're going too short...
522 #FIXME: 0.1 and 6 here are "magic numbers" (although reasonable)
523 if (abs(regr[1]) > 0.1) and ( endlength < len(xret)-int(len(xret)/6) ) :
529 ymean=np.mean(ychunk) #baseline
534 #find the first point below the calculated baseline
540 #The algorithm didn't find anything below the baseline! It should NEVER happen
548 def find_contact_point2(self, debug=False):
550 TO BE DEVELOPED IN THE FUTURE
551 Finds the contact point on the curve.
553 FIXME: should be moved, probably to generalvclamp.py
556 #raw_plot=self.current.curve.default_plots()[0]
557 raw_plot=self.plots[0]
558 '''xext=self.plots[0].vectors[0][0]
559 yext=self.plots[0].vectors[0][1]
560 xret2=self.plots[0].vectors[1][0]
561 yret=self.plots[0].vectors[1][1]
563 xext=raw_plot.vectors[0][0]
564 yext=raw_plot.vectors[0][1]
565 xret2=raw_plot.vectors[1][0]
566 yret=raw_plot.vectors[1][1]
568 first_point=[xext[0], yext[0]]
569 last_point=[xext[-1], yext[-1]]
571 #regr=scipy.polyfit(first_point, last_point,1)[0:2]
572 diffx=abs(first_point[0]-last_point[0])
573 diffy=abs(first_point[1]-last_point[1])
575 #using polyfit results in numerical errors. good old algebra.
577 b=first_point[1]-(a*first_point[0])
578 baseline=scipy.polyval((a,b), xext)
580 ysub=[item-basitem for item,basitem in zip(yext,baseline)]
582 contact=ysub.index(min(ysub))
584 return xext,ysub,contact
586 #now, exploit a ClickedPoint instance to calculate index...
588 dummy.absolute_coords=(x_intercept,y_intercept)
589 dummy.find_graph_coords(xret2,yret)
592 return dummy.index, regr, regr_contact
598 def x_do_contact(self,args):
600 DEBUG COMMAND to be activated in the future
602 xext,ysub,contact=self.find_contact_point2(debug=True)
604 contact_plot=self.plots[0]
605 contact_plot.add_set(xext,ysub)
606 contact_plot.add_set([xext[contact]],[self.plots[0].vectors[0][1][contact]])
607 #contact_plot.add_set([first_point[0]],[first_point[1]])
608 #contact_plot.add_set([last_point[0]],[last_point[1]])
609 contact_plot.styles=[None,None,None,'scatter']
610 self._send_plot([contact_plot])
614 index,regr,regr_contact=self.find_contact_point2(debug=True)
617 raw_plot=self.current.curve.default_plots()[0]
618 xret=raw_plot.vectors[0][0]
619 #nc_line=[(item*regr[0])+regr[1] for item in x_nc]
620 nc_line=scipy.polyval(regr,xret)
621 c_line=scipy.polyval(regr_contact,xret)
624 contact_plot=self.current.curve.default_plots()[0]
625 contact_plot.add_set(xret, nc_line)
626 contact_plot.add_set(xret, c_line)
627 contact_plot.styles=[None,None,None,None]
628 #contact_plot.styles.append(None)
629 contact_plot.destination=1
630 self._send_plot([contact_plot])