Merged with trunk
[hooke.git] / hooke / plugin / fit.py
old mode 100755 (executable)
new mode 100644 (file)
similarity index 94%
rename from fit.py
rename to hooke/plugin/fit.py
index 12f04e9..767110e
--- a/fit.py
@@ -10,13 +10,15 @@ Licensed under the GNU GPL version 2
 Non-standard Dependencies:
 procplots.py (plot processing plugin)
 '''
-from libhooke import WX_GOOD, ClickedPoint
+from ..libhooke import WX_GOOD, ClickedPoint
+
 import wxversion
 wxversion.select(WX_GOOD)
 #from wx import PostEvent
 #from wx.lib.newevent import NewEvent
 import scipy
 import scipy.odr
+import scipy.stats
 import numpy as np
 import copy
 import Queue
@@ -30,8 +32,8 @@ global events_from_fit
 events_from_fit=Queue.Queue() #GUI ---> CLI COMMUNICATION
 
 
-class fitCommands:
-    
+class fitCommands(object):
+
     def _plug_init(self):
         self.wlccurrent=None
         self.wlccontact_point=None
@@ -50,43 +52,43 @@ class fitCommands:
         The function is the simple polynomial worm-like chain as proposed by C.Bustamante, J.F.Marko, E.D.Siggia
         and S.Smith (Science. 1994 Sep 9;265(5178):1599-600.)
         '''
-    
+
         '''
         clicked_points[0] = contact point (calculated or hand-clicked)
         clicked_points[1] and [2] are edges of chunk
         '''
-    
+
         #STEP 1: Prepare the vectors to apply the fit.
         if pl_value is not None:
             pl_value=pl_value/(10**9)
-        
+
         #indexes of the selected chunk
         first_index=min(clicked_points[1].index, clicked_points[2].index)
         last_index=max(clicked_points[1].index, clicked_points[2].index)
-               
+
         #getting the chunk and reverting it
         xchunk,ychunk=xvector[first_index:last_index],yvector[first_index:last_index]
         xchunk.reverse()
-        ychunk.reverse()    
+        ychunk.reverse()
         #put contact point at zero and flip around the contact point (the fit wants a positive growth for extension and force)
         xchunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xchunk]
         ychunk_corr_up=[-(y-clicked_points[0].graph_coords[1]) for y in ychunk]
-        
+
         #make them arrays
         xchunk_corr_up=scipy.array(xchunk_corr_up)
         ychunk_corr_up=scipy.array(ychunk_corr_up)
-    
-        
+
+
         #STEP 2: actually do the fit
-    
+
         #Find furthest point of chunk and add it a bit; the fit must converge
         #from an excess!
         xchunk_high=max(xchunk_corr_up)
         xchunk_high+=(xchunk_high/10)
-    
+
         #Here are the linearized start parameters for the WLC.
         #[lambd=1/Lo , pii=1/P]
-    
+
         p0=[(1/xchunk_high),(1/(3.5e-10))]
         p0_plfix=[(1/xchunk_high)]
         '''
@@ -108,7 +110,7 @@ class fitCommands:
             therm=Kb*T
             y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd))
             return y
-        
+
         def f_wlc_plfix(params,x,pl_value=pl_value,T=T):
             '''
             wlc function for ODR fitting
@@ -119,7 +121,7 @@ class fitCommands:
             therm=Kb*T
             y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd))
             return y
-        
+
         #make the ODR fit
         realdata=scipy.odr.RealData(xchunk_corr_up,ychunk_corr_up)
         if pl_value:
@@ -128,11 +130,11 @@ class fitCommands:
         else:
             model=scipy.odr.Model(f_wlc)
             o = scipy.odr.ODR(realdata, model, p0)
-        
+
         o.set_job(fit_type=2)
         out=o.run()
         fit_out=[(1/i) for i in out.beta]
-        
+
         #Calculate fit errors from output standard deviations.
         #We must propagate the error because we fit the *inverse* parameters!
         #The error = (error of the inverse)*(value**2)
@@ -140,8 +142,8 @@ class fitCommands:
         for sd,value in zip(out.sd_beta, fit_out):
             err_real=sd*(value**2)
             fit_errors.append(err_real)
-        
-        def wlc_eval(x,params,pl_value,T):    
+
+        def wlc_eval(x,params,pl_value,T):
             '''
             Evaluates the WLC function
             '''
@@ -149,18 +151,18 @@ class fitCommands:
                 lambd, pii = params
             else:
                 lambd = params
-        
+
             if pl_value:
                 pii=1/pl_value
-        
+
             Kb=(1.38065e-23) #boltzmann constant
             therm=Kb*T #so we have thermal energy
-        
+
             return ( (therm*pii/4.0) * (((1-(x*lambd))**-2.0) - 1 + (4.0*x*lambd)) )
         
         
         #STEP 3: plotting the fit
-        
+
         #obtain domain to plot the fit - from contact point to last_index plus 20 points
         thule_index=last_index+10
         if thule_index > len(xvector): #for rare cases in which we fit something at the END of whole curve.
@@ -170,7 +172,7 @@ class fitCommands:
         xfit_chunk.reverse()
         xfit_chunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xfit_chunk]
         xfit_chunk_corr_up=scipy.array(xfit_chunk_corr_up)
-    
+
         #the fitted curve: reflip, re-uncorrect
         yfit=wlc_eval(xfit_chunk_corr_up, out.beta, pl_value,T)
         yfit_down=[-y for y in yfit]
@@ -200,39 +202,39 @@ class fitCommands:
         clicked_points[0] is the contact point (calculated or hand-clicked)
         clicked_points[1] and [2] are edges of chunk
         '''
-        
+
         #STEP 1: Prepare the vectors to apply the fit.
         if pl_value is not None:
             pl_value=pl_value/(10**9)
-        
+
         #indexes of the selected chunk
         first_index=min(clicked_points[1].index, clicked_points[2].index)
         last_index=max(clicked_points[1].index, clicked_points[2].index)
-        
+
         #getting the chunk and reverting it
         xchunk,ychunk=xvector[first_index:last_index],yvector[first_index:last_index]
         xchunk.reverse()
-        ychunk.reverse()    
+        ychunk.reverse()
         #put contact point at zero and flip around the contact point (the fit wants a positive growth for extension and force)
         xchunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xchunk]
         ychunk_corr_up=[-(y-clicked_points[0].graph_coords[1]) for y in ychunk]
-        
-        
+
+
         #make them arrays
         xchunk_corr_up=scipy.array(xchunk_corr_up)
         ychunk_corr_up=scipy.array(ychunk_corr_up)
-    
-        
+
+
         #STEP 2: actually do the fit
-    
+
         #Find furthest point of chunk and add it a bit; the fit must converge
         #from an excess!
         xchunk_high=max(xchunk_corr_up)
         xchunk_high+=(xchunk_high/10)
-    
+
         #Here are the linearized start parameters for the WLC.
         #[lambd=1/Lo , pii=1/P]
-    
+
         p0=[(1/xchunk_high),(1/(3.5e-10))]
         p0_plfix=[(1/xchunk_high)]
         '''
@@ -250,7 +252,7 @@ class fitCommands:
             hyperbolic cotangent
             '''
             return (np.exp(2*z)+1)/(np.exp(2*z)-1)
-        
+
         def x_fjc(params,f,T=T):
             '''
             fjc function for ODR fitting
@@ -258,11 +260,11 @@ class fitCommands:
             lambd,pii=params
             Kb=(1.38065e-23)
             therm=Kb*T
-            
+
             #x=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd))
             x=(1/lambd)*(coth(f*(1/pii)/therm) - (therm*pii)/f)
             return x
-        
+
         def x_fjc_plfix(params,f,pl_value=pl_value,T=T):
             '''
             fjc function for ODR fitting
@@ -274,7 +276,7 @@ class fitCommands:
             #y=(therm*pii/4.0) * (((1-(x*lambd))**-2) - 1 + (4*x*lambd))
             x=(1/lambd)*(coth(f*(1/pii)/therm) - (therm*pii)/f)
             return x
-        
+
         #make the ODR fit
         realdata=scipy.odr.RealData(ychunk_corr_up,xchunk_corr_up)
         if pl_value:
@@ -283,11 +285,11 @@ class fitCommands:
         else:
             model=scipy.odr.Model(x_fjc)
             o = scipy.odr.ODR(realdata, model, p0)
-        
+
         o.set_job(fit_type=2)
         out=o.run()
         fit_out=[(1/i) for i in out.beta]
-        
+
         #Calculate fit errors from output standard deviations.
         #We must propagate the error because we fit the *inverse* parameters!
         #The error = (error of the inverse)*(value**2)
@@ -295,8 +297,8 @@ class fitCommands:
         for sd,value in zip(out.sd_beta, fit_out):
             err_real=sd*(value**2)
             fit_errors.append(err_real)
-        
-        def fjc_eval(y,params,pl_value,T):    
+
+        def fjc_eval(y,params,pl_value,T):
             '''
             Evaluates the WLC function
             '''
@@ -304,16 +306,17 @@ class fitCommands:
                 lambd, pii = params
             else:
                 lambd = params
-        
+
             if pl_value:
                 pii=1/pl_value
-        
+
             Kb=(1.38065e-23) #boltzmann constant
             therm=Kb*T #so we have thermal energy
             #return ( (therm*pii/4.0) * (((1-(x*lambd))**-2.0) - 1 + (4.0*x*lambd)) )
             return (1/lambd)*(coth(y*(1/pii)/therm) - (therm*pii)/y)
-           
-        
+
+
+
         #STEP 3: plotting the fit
         #obtain domain to plot the fit - from contact point to last_index plus 20 points
         thule_index=last_index+10
@@ -329,27 +332,27 @@ class fitCommands:
             #or other buggy situations. Kludge to live with it now...
             ychunk=yvector[:thule_index]
             y_evalchunk=np.linspace(min(ychunk),max(ychunk),100)
-            
+
         yfit_down=[-y for y in y_evalchunk]
         yfit_corr_down=[y+clicked_points[0].graph_coords[1] for y in yfit_down]
         yfit_corr_down=scipy.array(yfit_corr_down)
-        
+
         #the fitted curve: reflip, re-uncorrect
         xfit=fjc_eval(yfit_corr_down, out.beta, pl_value,T)
         xfit=list(xfit)
         xfit.reverse()
         xfit_chunk_corr_up=[-(x-clicked_points[0].graph_coords[0]) for x in xfit]
-        
+
         #xfit_chunk_corr_up=scipy.array(xfit_chunk_corr_up)
         #deltay=yfit_down[0]-yvector[clicked_points[0].index]
-        
+
         #This is a terrible, terrible kludge to find the point where it should normalize (and from where it should plot)
         xxxdists=[]
         for index in scipy.arange(1,len(xfit_chunk_corr_up),1):
-            xxxdists.append((clicked_points[0].graph_coords[0]-xfit_chunk_corr_up[index])**2)           
+            xxxdists.append((clicked_points[0].graph_coords[0]-xfit_chunk_corr_up[index])**2)
         normalize_index=xxxdists.index(min(xxxdists))
         #End of kludge
-        
+
         deltay=yfit_down[normalize_index]-clicked_points[0].graph_coords[1]
         yfit_corr_down=[y-deltay for y in yfit_down]
         
@@ -594,20 +597,20 @@ class fitCommands:
         '''
         WLC
         (fit.py plugin)
-        
+
         See the fit command
         '''
         self.do_fit(args)
-    
+
     def do_fjc(self,args):
         '''
         FJC
         (fit.py plugin)
-        
+
         See the fit command
         '''
         self.do_fit(args)
-    
+
     def do_fit(self,args):
         '''
         FIT
@@ -621,12 +624,12 @@ class fitCommands:
         
         The fit function depends on the fit_function variable. You can set it with the command
         "set fit_function wlc" or  "set fit_function fjc" depending on the function you prefer.
-        
-        For WLC, the function is the simple polynomial worm-like chain as proposed by 
-        C.Bustamante, J.F.Marko, E.D.Siggia and S.Smith (Science. 1994 
+
+        For WLC, the function is the simple polynomial worm-like chain as proposed by
+        C.Bustamante, J.F.Marko, E.D.Siggia and S.Smith (Science. 1994
         Sep 9;265(5178):1599-600.)
-        
-        For FJC, ref: 
+
+        For FJC, ref:
         C.Ray and B.B. Akhremitchev; http://www.chem.duke.edu/~boris/research/force_spectroscopy/fit_efjc.pdf
         
         For eFJC, ref:
@@ -634,20 +637,20 @@ class fitCommands:
         NOTE: use fixed pl for better results.
 
         Arguments:
-        pl=[value] : Use a fixed persistent length (WLC) or Kuhn length (FJC) for the fit. If pl is not given, 
-                     the fit will be a 2-variable  
+        pl=[value] : Use a fixed persistent length (WLC) or Kuhn length (FJC) for the fit. If pl is not given,
+                     the fit will be a 2-variable
                      fit. DO NOT put spaces between 'pl', '=' and the value.
-                     The value must be in nanometers. 
-        
+                     The value must be in nanometers.
+
         t=[value] : Use a user-defined temperature. The value must be in
                     kelvins; by default it is 293 K.
                     DO NOT put spaces between 't', '=' and the value.
-        
-        noauto : allows for clicking the contact point by 
+
+        noauto : allows for clicking the contact point by
                  hand (otherwise it is automatically estimated) the first time.
                  If subsequent measurements are made, the same contact point
                  clicked is used
-        
+
         reclick : redefines by hand the contact point, if noauto has been used before
                   but the user is unsatisfied of the previously choosen contact point.
         ---------
@@ -664,10 +667,10 @@ class fitCommands:
             if ('t=' in arg[0:2]) or ('T=' in arg[0:2]):
                 t_expression=arg.split('=')
                 T=float(t_expression[1])
-        
+
         #use the currently displayed plot for the fit
         displayed_plot=self._get_displayed_plot()
-               
+
         #handle contact point arguments correctly
         if 'reclick' in args.split():
             print 'Click contact point'
@@ -693,7 +696,7 @@ class fitCommands:
             contact_point.absolute_coords=displayed_plot.vectors[1][0][cindex], displayed_plot.vectors[1][1][cindex]
             contact_point.find_graph_coords(displayed_plot.vectors[1][0], displayed_plot.vectors[1][1])
             contact_point.is_marker=True
-            
+
         print 'Click edges of chunk'
         points=self._measure_N_points(N=2, whatset=1)
         points=[contact_point]+points
@@ -712,28 +715,28 @@ class fitCommands:
                 print 'No recognized fit function defined!'
                 print 'Set your fit function to wlc, fjc or efjc.'
                 return
-            
+
         except:
             print 'Fit not possible. Probably wrong interval -did you click two *different* points?'
             return
-        
+
         #FIXME: print "Kuhn length" for FJC
         print 'Fit function:',self.config['fit_function']
-        print 'Contour length: %.2f nm' %(params[0]*(1.0e+9))
-        to_dump='contour '+self.current.path+' %.2f nm'%(params[0]*(1.0e+9))
+        print 'Contour length: ',params[0]*(1.0e+9),' nm'
+        to_dump='contour '+self.current.path+' '+str(params[0]*(1.0e+9))+' nm'
         self.outlet.push(to_dump)
         if len(params)==2: #if we did choose 2-value fit
-            print name_of_charlength+': %.2f nm' %(params[1]*(1.0e+9))
-            to_dump='persistent '+self.current.path+' %.2f nm' %(params[1]*(1.0e+9))
+            print name_of_charlength+': ',params[1]*(1.0e+9),' nm'
+            to_dump='persistent '+self.current.path+' '+str(params[1]*(1.0e+9))+' nm'
             self.outlet.push(to_dump)
-        
+
         if fit_errors:
             fit_nm=[i*(10**9) for i in fit_errors]
-            print 'Standard deviation (contour length) %.2f' %fit_nm[0]
+            print 'Standard deviation (contour length)', fit_nm[0]
             if len(fit_nm)>1:
-                print 'Standard deviation ('+name_of_charlength+') %.2f' %fit_nm[1]
+                print 'Standard deviation ('+name_of_charlength+')', fit_nm[1]
         
-        print 'Fit quality: %.3f ' %(qstd/np.std(displayed_plot.vectors[1][1][-20:-1]))
+        print 'Fit quality: '+str(qstd/np.std(displayed_plot.vectors[1][1][-20:-1]))
             
             
         #add the clicked points in the final PlotObject
@@ -741,51 +744,51 @@ class fitCommands:
         for item in points:
             clickvector_x.append(item.graph_coords[0])
             clickvector_y.append(item.graph_coords[1])
-        
+
         #create a custom PlotObject to gracefully plot the fit along the curves
-                        
+
         fitplot=copy.deepcopy(displayed_plot)
         fitplot.add_set(xfit,yfit)
         fitplot.add_set(clickvector_x,clickvector_y)
-        
+
         #FIXME: this colour/styles stuff must be solved at the root!
         if fitplot.styles==[]:
             fitplot.styles=[None,None,None,'scatter']
         else:
             fitplot.styles+=[None,'scatter']
-        
+
         if fitplot.colors==[]:
             fitplot.colors=[None,None,None,None]
         else:
             fitplot.colors+=[None,None]
-        
+
         self._send_plot([fitplot])
-    
-  
+
+
 
     #----------
-    
-    
+
+
     def find_contact_point(self,plot=False):
         '''
         Finds the contact point on the curve.
-    
+
         The current algorithm (thanks to Francesco Musiani, francesco.musiani@unibo.it and Massimo Sandal) is:
         - take care of the PicoForce trigger bug - exclude retraction portions with too high standard deviation
         - fit the second half of the retraction curve to a line
         - if the fit is not almost horizontal, take a smaller chunk and repeat
         - otherwise, we have something horizontal
         - so take the average of horizontal points and use it as a baseline
-    
+
         Then, start from the rise of the retraction curve and look at the first point below the
         baseline.
-        
+
         FIXME: should be moved, probably to generalvclamp.py
         '''
-        
+
         if not plot:
             plot=self.plots[0]
-        
+
         outplot=self.subtract_curves(1)
         xret=outplot.vectors[1][0]
         ydiff=outplot.vectors[1][1]
@@ -794,7 +797,7 @@ class fitCommands:
         yext=plot.vectors[0][1]
         xret2=plot.vectors[1][0]
         yret=plot.vectors[1][1]
-        
+
         #taking care of the picoforce trigger bug: we exclude portions of the curve that have too much
         #standard deviation. yes, a lot of magic is here.
         monster=True
@@ -807,13 +810,13 @@ class fitCommands:
             else: #move away from the monster
                 monlength-=int(len(xret)/50)
                 finalength-=int(len(xret)/50)
-    
-    
+
+
         #take half of the thing
         endlength=int(len(xret)/2)
-    
+
         ok=False
-        
+
         while not ok:
             xchunk=yext[endlength:monlength]
             ychunk=yext[endlength:monlength]
@@ -823,36 +826,36 @@ class fitCommands:
             if (abs(regr[1]) > 0.1) and ( endlength < len(xret)-int(len(xret)/6) ) :
                 endlength+=10
             else:
-                ok=True  
-                  
-        
+                ok=True
+
+
         ymean=np.mean(ychunk) #baseline
-    
+
         index=0
         point = ymean+1
-    
+
         #find the first point below the calculated baseline
         while point > ymean:
             try:
                 point=yret[index]
-                index+=1    
+                index+=1
             except IndexError:
                 #The algorithm didn't find anything below the baseline! It should NEVER happen
-                index=0            
+                index=0
                 return index
-            
+
         return index
-                        
-    
-    
+
+
+
     def find_contact_point2(self, debug=False):
         '''
         TO BE DEVELOPED IN THE FUTURE
         Finds the contact point on the curve.
-            
+
         FIXME: should be moved, probably to generalvclamp.py
         '''
-        
+
         #raw_plot=self.current.curve.default_plots()[0]
         raw_plot=self.plots[0]
         '''xext=self.plots[0].vectors[0][0]
@@ -864,43 +867,43 @@ class fitCommands:
         yext=raw_plot.vectors[0][1]
         xret2=raw_plot.vectors[1][0]
         yret=raw_plot.vectors[1][1]
-        
+
         first_point=[xext[0], yext[0]]
         last_point=[xext[-1], yext[-1]]
-       
+
         #regr=scipy.polyfit(first_point, last_point,1)[0:2]
         diffx=abs(first_point[0]-last_point[0])
         diffy=abs(first_point[1]-last_point[1])
-        
+
         #using polyfit results in numerical errors. good old algebra.
         a=diffy/diffx
         b=first_point[1]-(a*first_point[0])
         baseline=scipy.polyval((a,b), xext)
-        
+
         ysub=[item-basitem for item,basitem in zip(yext,baseline)]
-        
+
         contact=ysub.index(min(ysub))
-        
+
         return xext,ysub,contact
-        
+
         #now, exploit a ClickedPoint instance to calculate index...
         dummy=ClickedPoint()
         dummy.absolute_coords=(x_intercept,y_intercept)
         dummy.find_graph_coords(xret2,yret)
-        
+
         if debug:
             return dummy.index, regr, regr_contact
         else:
             return dummy.index
-            
-        
+
+
 
     def x_do_contact(self,args):
         '''
         DEBUG COMMAND to be activated in the future
         '''
         xext,ysub,contact=self.find_contact_point2(debug=True)
-        
+
         contact_plot=self.plots[0]
         contact_plot.add_set(xext,ysub)
         contact_plot.add_set([xext[contact]],[self.plots[0].vectors[0][1][contact]])
@@ -909,8 +912,8 @@ class fitCommands:
         contact_plot.styles=[None,None,None,'scatter']
         self._send_plot([contact_plot])
         return
-        
-        
+
+
         index,regr,regr_contact=self.find_contact_point2(debug=True)
         print regr
         print regr_contact
@@ -919,8 +922,8 @@ class fitCommands:
         #nc_line=[(item*regr[0])+regr[1] for item in x_nc]
         nc_line=scipy.polyval(regr,xret)
         c_line=scipy.polyval(regr_contact,xret)
-                     
-        
+
+
         contact_plot=self.current.curve.default_plots()[0]
         contact_plot.add_set(xret, nc_line)
         contact_plot.add_set(xret, c_line)
@@ -928,4 +931,3 @@ class fitCommands:
         #contact_plot.styles.append(None)
         contact_plot.destination=1
         self._send_plot([contact_plot])
-        
\ No newline at end of file