1 # Copyright (C) 2008-2010 Alberto Gomez-Casado
4 # Massimo Sandal <devicerandom@gmail.com>
5 # W. Trevor King <wking@drexel.edu>
7 # This file is part of Hooke.
9 # Hooke is free software: you can redistribute it and/or modify it
10 # under the terms of the GNU Lesser General Public License as
11 # published by the Free Software Foundation, either version 3 of the
12 # License, or (at your option) any later version.
14 # Hooke is distributed in the hope that it will be useful, but WITHOUT
15 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
17 # Public License for more details.
19 # You should have received a copy of the GNU Lesser General Public
20 # License along with Hooke. If not, see
21 # <http://www.gnu.org/licenses/>.
23 """The ``vclamp`` module provides :class:`VelocityClampPlugin` and
24 several associated :class:`hooke.command.Command`\s for handling
25 common velocity clamp analysis tasks.
33 from ..command import Command, Argument, Failure, NullQueue
34 from ..config import Setting
35 from ..curve import Data
36 from ..plugin import Plugin
37 from ..util.fit import PoorFit, ModelFitter
38 from .curve import CurveArgument
41 def scale(hooke, curve, block=None):
42 """Run 'add block force array' on `block`.
44 Runs 'zero block surface contact point' first, if necessary. Does
45 not run either command if the columns they add to the block are
48 If `block` is `None`, scale all blocks in `curve`.
50 commands = hooke.commands
51 contact = [c for c in hooke.commands
52 if c.name == 'zero block surface contact point'][0]
53 force = [c for c in hooke.commands if c.name == 'add block force array'][0]
55 outqueue = NullQueue()
57 for i in range(len(curve.data)):
58 scale(hooke, curve, block=i)
60 params = {'curve':curve, 'block':block}
62 if ('surface distance (m)' not in b.info.columns
63 or 'surface adjusted deflection (m)' not in b.info.columns):
65 contact._run(hooke, inqueue, outqueue, params)
67 raise PoorFit('Could not fit %s %s: %s'
68 % (curve.path, block, str(e)))
69 if ('deflection (N)' not in b.info.columns):
70 force._run(hooke, inqueue, outqueue, params)
73 class SurfacePositionModel (ModelFitter):
76 The bilinear model is symmetric, but the parameter guessing and
77 sanity checks assume the contact region occurs for lower indicies
78 ("left of") the non-contact region. We also assume that
79 tip-surface attractions produce positive deflections.
83 Algorithm borrowed from WTK's `piezo package`_, specifically
84 from :func:`piezo.z_piezo_utils.analyzeSurfPosData`.
87 http://www.physics.drexel.edu/~wking/code/git/git.php?p=piezo.git
89 Fits the data to the bilinear :method:`model`.
91 In order for this model to produce a satisfactory fit, there
92 should be enough data in the off-surface region that interactions
93 due to proteins, etc. will not seriously skew the fit in the
99 def model(self, params):
100 """A continuous, bilinear model.
107 p_0 + p_1 x & \text{if $x <= p_2$}, \\
108 p_0 + p_1 p_2 + p_3 (x-p_2) & \text{if $x >= p_2$}.
111 Where :math:`p_0` is a vertical offset, :math:`p_1` is the slope
112 of the first region, :math:`p_2` is the transition location, and
113 :math:`p_3` is the slope of the second region.
115 p = params # convenient alias
116 if self.info.get('force zero non-contact slope', None) == True:
118 p.append(0.) # restore the non-contact slope parameter
119 r2 = numpy.round(abs(p[2]))
121 self._model_data[:r2] = p[0] + p[1] * numpy.arange(r2)
122 if r2 < len(self._data)-1:
123 self._model_data[r2:] = \
124 p[0] + p[1]*p[2] + p[3] * numpy.arange(len(self._data)-r2)
125 return self._model_data
127 def set_data(self, data, info=None):
128 super(SurfacePositionModel, self).set_data(data, info)
132 self.info['min position'] = 0
133 self.info['max position'] = len(data)
134 self.info['max deflection'] = data.max()
135 self.info['min deflection'] = data.min()
136 self.info['position range'] = self.info['max position'] - self.info['min position']
137 self.info['deflection range'] = self.info['max deflection'] - self.info['min deflection']
139 def guess_initial_params(self, outqueue=None):
140 """Guess the initial parameters.
144 We guess initial parameters such that the offset (:math:`p_1`)
145 matches the minimum deflection, the kink (:math:`p_2`) occurs in
146 the middle of the data, the initial (contact) slope (:math:`p_0`)
147 produces the maximum deflection at the left-most point, and the
148 final (non-contact) slope (:math:`p_3`) is zero.
150 left_offset = self.info['min deflection']
151 left_slope = 2*(self.info['deflection range']
152 /self.info['position range'])
153 kink_position = (self.info['max position']
154 +self.info['min position'])/2.0
156 self.info['guessed contact slope'] = left_slope
157 params = [left_offset, left_slope, kink_position, right_slope]
158 if self.info.get('force zero non-contact slope', None) == True:
162 def guess_scale(self, params, outqueue=None):
163 """Guess the parameter scales.
168 We guess offset scale (:math:`p_0`) as one tenth of the total
169 deflection range, the kink scale (:math:`p_2`) as one tenth of
170 the total index range, the initial (contact) slope scale
171 (:math:`p_1`) as one tenth of the contact slope estimation,
172 and the final (non-contact) slope scale (:math:`p_3`) is as
173 one tenth of the initial slope scale.
175 offset_scale = self.info['deflection range']/10.
176 left_slope_scale = abs(params[1])/10.
177 kink_scale = self.info['position range']/10.
178 right_slope_scale = left_slope_scale/10.
179 scale = [offset_scale, left_slope_scale, kink_scale, right_slope_scale]
180 if self.info.get('force zero non-contact slope', None) == True:
184 def fit(self, *args, **kwargs):
185 self.info['guessed contact slope'] = None
186 params = super(SurfacePositionModel, self).fit(*args, **kwargs)
187 params[2] = abs(params[2])
188 if self.info.get('force zero non-contact slope', None) == True:
189 params = list(params)
190 params.append(0.) # restore the non-contact slope parameter
192 # check that the fit is reasonable, see the :meth:`model` docstring
193 # for parameter descriptions. HACK: hardcoded cutoffs.
194 if abs(params[3]*10) > abs(params[1]) :
195 raise PoorFit('Slope in non-contact region, or no slope in contact')
196 if params[2] < self.info['min position']+0.02*self.info['position range']:
198 'No kink (kink %g less than %g, need more space to left)'
200 self.info['min position']+0.02*self.info['position range']))
201 if params[2] > self.info['max position']-0.02*self.info['position range']:
203 'No kink (kink %g more than %g, need more space to right)'
205 self.info['max position']-0.02*self.info['position range']))
206 if (self.info['guessed contact slope'] != None
207 and abs(params[1]) < 0.5 * abs(self.info['guessed contact slope'])):
208 raise PoorFit('Too far (contact slope %g, but expected ~%g'
209 % (params[3], self.info['guessed contact slope']))
212 class VelocityClampPlugin (Plugin):
214 super(VelocityClampPlugin, self).__init__(name='vclamp')
216 SurfaceContactCommand(self), ForceCommand(self),
219 def default_settings(self):
221 Setting(section=self.setting_section, help=self.__doc__),
222 Setting(section=self.setting_section,
223 option='surface contact point algorithm',
225 help='Select the surface contact point algorithm. See the documentation for descriptions of available algorithms.')
229 class SurfaceContactCommand (Command):
230 """Automatically determine a block's surface contact point.
232 Uses the block's `z piezo (m)` and `deflection (m)` arrays.
233 Stores the contact parameters in `block.info`'s `surface distance
234 offset (m)` and `surface deflection offset (m)`. Model-specific
235 fitting information is stored in `surface detection parameters`.
237 The adjusted data columns `surface distance (m)` and `surface
238 adjusted deflection (m)` are also added to the block.
240 You can select the contact point algorithm with the creatively
241 named `surface contact point algorithm` configuration setting.
242 Currently available options are:
244 * fmms (:meth:`find_contact_point_fmms`)
245 * ms (:meth:`find_contact_point_ms`)
246 * wtk (:meth:`find_contact_point_wtk`)
248 def __init__(self, plugin):
249 super(SurfaceContactCommand, self).__init__(
250 name='zero block surface contact point',
253 Argument(name='block', aliases=['set'], type='int', default=0,
255 Data block for which the force should be calculated. For an
256 approach/retract force curve, `0` selects the approaching curve and `1`
257 selects the retracting curve.
260 help=self.__doc__, plugin=plugin)
262 def _run(self, hooke, inqueue, outqueue, params):
263 data = params['curve'].data[params['block']]
264 # HACK? rely on params['curve'] being bound to the local hooke
265 # playlist (i.e. not a copy, as you would get by passing a
266 # curve through the queue). Ugh. Stupid queues. As an
267 # alternative, we could pass lookup information through the
269 new = Data((data.shape[0], data.shape[1]+2), dtype=data.dtype)
270 new.info = copy.deepcopy(data.info)
272 new.info['columns'].extend(
273 ['surface distance (m)', 'surface adjusted deflection (m)'])
274 z_data = data[:,data.info['columns'].index('z piezo (m)')]
275 d_data = data[:,data.info['columns'].index('deflection (m)')]
276 i,deflection_offset,ps = self.find_contact_point(
277 params['curve'], z_data, d_data, outqueue)
278 surface_offset = z_data[i]
279 new.info['surface distance offset (m)'] = surface_offset
280 new.info['surface deflection offset (m)'] = deflection_offset
281 new.info['surface detection parameters'] = ps
282 new[:,-2] = z_data - surface_offset
283 new[:,-1] = d_data - deflection_offset
284 data = params['curve'].data[params['block']]
285 params['curve'].data[params['block']] = new
287 def find_contact_point(self, curve, z_data, d_data, outqueue=None):
288 """Railyard for the `find_contact_point_*` family.
290 Uses the `surface contact point algorithm` configuration
291 setting to call the appropriate backend algorithm.
293 fn = getattr(self, 'find_contact_point_%s'
294 % self.plugin.config['surface contact point algorithm'])
295 return fn(curve, z_data, d_data, outqueue)
297 def find_contact_point_fmms(self, curve, z_data, d_data, outqueue=None):
298 """Algorithm by Francesco Musiani and Massimo Sandal.
304 0) Driver-specific workarounds, e.g. deal with the PicoForce
305 trigger bug by excluding retraction portions with excessive
307 1) Select the second half (non-contact side) of the retraction
309 2) Fit the selection to a line.
310 3) If the fit is not almost horizontal, halve the selection
312 4) Average the selection and use it as a baseline.
313 5) Slide in from the start (contact side) of the retraction
314 curve, until you find a point with greater than baseline
315 deflection. That point is the contact point.
317 if curve.info['filetype'] == 'picoforce':
318 # Take care of the picoforce trigger bug (TODO: example
319 # data file demonstrating the bug). We exclude portions
320 # of the curve that have too much standard deviation.
321 # Yes, a lot of magic is here.
322 check_start = len(d_data)-len(d_data)/20
323 monster_start = len(d_data)
325 # look at the non-contact tail
326 non_monster = d_data[check_start:monster_start]
327 if non_monster.std() < 2e-10: # HACK: hardcoded cutoff
329 else: # move further away from the monster
330 check_start -= len(d_data)/50
331 monster_start -= len(d_data)/50
332 z_data = z_data[:monster_start]
333 d_data = d_data[:monster_start]
335 # take half of the thing to start
336 selection_start = len(d_data)/2
338 z_chunk = z_data[selection_start:]
339 d_chunk = d_data[selection_start:]
340 slope,intercept,r,two_tailed_prob,stderr_of_the_estimate = \
341 scipy.stats.linregress(z_chunk, d_chunk)
342 # We stop if we found an almost-horizontal fit or if we're
343 # getting to small a selection. FIXME: 0.1 and 5./6 here
344 # are "magic numbers" (although reasonable)
345 if (abs(slope) < 0.1 # deflection (m) / surface (m)
346 or selection_start > 5./6*len(d_data)):
348 selection_start += 10
350 d_baseline = d_chunk.mean()
352 # find the first point above the calculated baseline
354 while i < len(d_data) and d_data[i] < ymean:
356 return (i, d_baseline, {})
358 def find_contact_point_ms(self, curve, z_data, d_data, outqueue=None):
359 """Algorithm by Massimo Sandal.
363 WTK: At least the commits are by Massimo, and I see no notes
364 attributing the algorithm to anyone else.
370 xext=raw_plot.vectors[0][0]
371 yext=raw_plot.vectors[0][1]
372 xret2=raw_plot.vectors[1][0]
373 yret=raw_plot.vectors[1][1]
375 first_point=[xext[0], yext[0]]
376 last_point=[xext[-1], yext[-1]]
378 #regr=scipy.polyfit(first_point, last_point,1)[0:2]
379 diffx=abs(first_point[0]-last_point[0])
380 diffy=abs(first_point[1]-last_point[1])
382 #using polyfit results in numerical errors. good old algebra.
384 b=first_point[1]-(a*first_point[0])
385 baseline=scipy.polyval((a,b), xext)
387 ysub=[item-basitem for item,basitem in zip(yext,baseline)]
389 contact=ysub.index(min(ysub))
391 return xext,ysub,contact
393 #now, exploit a ClickedPoint instance to calculate index...
395 dummy.absolute_coords=(x_intercept,y_intercept)
396 dummy.find_graph_coords(xret2,yret)
399 return dummy.index, regr, regr_contact
403 def find_contact_point_wtk(self, curve, z_data, d_data, outqueue=None):
404 """Algorithm by W. Trevor King.
408 Uses :func:`analyze_surf_pos_data_wtk` internally.
410 reverse = z_data[0] > z_data[-1]
411 if reverse == True: # approaching, contact region on the right
412 d_data = d_data[::-1]
413 s = SurfacePositionModel(d_data)
414 s.info['force zero non-contact slope'] = True
415 offset,contact_slope,surface_index,non_contact_slope = s.fit(
419 'contact slope': contact_slope,
420 'surface index': surface_index,
421 'non-contact slope': non_contact_slope,
424 deflection_offset = offset + contact_slope*surface_index,
426 surface_index = len(d_data)-1-surface_index
427 return (numpy.round(surface_index), deflection_offset, info)
429 class ForceCommand (Command):
430 """Calculate a block's `deflection (N)` array.
432 Uses the block's `deflection (m)` array and `spring constant
435 def __init__(self, plugin):
436 super(ForceCommand, self).__init__(
437 name='add block force array',
440 Argument(name='block', aliases=['set'], type='int', default=0,
442 Data block for which the force should be calculated. For an
443 approach/retract force curve, `0` selects the approaching curve and `1`
444 selects the retracting curve.
447 help=self.__doc__, plugin=plugin)
449 def _run(self, hooke, inqueue, outqueue, params):
450 data = params['curve'].data[params['block']]
451 # HACK? rely on params['curve'] being bound to the local hooke
452 # playlist (i.e. not a copy, as you would get by passing a
453 # curve through the queue). Ugh. Stupid queues. As an
454 # alternative, we could pass lookup information through the
456 new = Data((data.shape[0], data.shape[1]+1), dtype=data.dtype)
457 new.info = copy.deepcopy(data.info)
459 new.info['columns'].append('deflection (N)')
460 d_data = data[:,data.info['columns'].index('surface adjusted deflection (m)')]
461 new[:,-1] = d_data * data.info['spring constant (N/m)']
462 params['curve'].data[params['block']] = new
465 class generalvclampCommands(object):
467 def do_subtplot(self, args):
470 (procplots.py plugin)
471 Plots the difference between ret and ext current curve
475 #FIXME: sub_filter and sub_order must be args
477 if len(self.plots[0].vectors) != 2:
478 print 'This command only works on a curve with two different plots.'
481 outplot=self.subtract_curves(sub_order=1)
483 plot_graph=self.list_of_events['plot_graph']
484 wx.PostEvent(self.frame,plot_graph(plots=[outplot]))
486 def _plug_init(self):
487 self.basecurrent=None
491 def do_distance(self,args):
495 Measure the distance (in nm) between two points.
496 For a standard experiment this is the delta X distance.
497 For a force clamp experiment this is the delta Y distance (actually becomes
502 if self.current.curve.experiment == 'clamp':
503 print 'You wanted to use zpiezo perhaps?'
506 dx,unitx,dy,unity=self._delta(set=1)
507 print str(dx*(10**9))+' nm'
508 to_dump='distance '+self.current.path+' '+str(dx*(10**9))+' nm'
509 self.outlet.push(to_dump)
512 def do_force(self,args):
516 Measure the force difference (in pN) between two points
520 if self.current.curve.experiment == 'clamp':
521 print 'This command makes no sense for a force clamp experiment.'
523 dx,unitx,dy,unity=self._delta(set=1)
524 print str(dy*(10**12))+' pN'
525 to_dump='force '+self.current.path+' '+str(dy*(10**12))+' pN'
526 self.outlet.push(to_dump)
529 def do_forcebase(self,args):
533 Measures the difference in force (in pN) between a point and a baseline
534 took as the average between two points.
536 The baseline is fixed once for a given curve and different force measurements,
537 unless the user wants it to be recalculated
539 Syntax: forcebase [rebase]
540 rebase: Forces forcebase to ask again the baseline
541 max: Instead of asking for a point to measure, asks for two points and use
542 the maximum peak in between
544 rebase=False #if true=we select rebase
545 maxpoint=False #if true=we measure the maximum peak
547 plot=self._get_displayed_plot()
548 whatset=1 #fixme: for all sets
549 if 'rebase' in args or (self.basecurrent != self.current.path):
555 print 'Select baseline'
556 self.basepoints=self._measure_N_points(N=2, whatset=whatset)
557 self.basecurrent=self.current.path
560 print 'Select two points'
561 points=self._measure_N_points(N=2, whatset=whatset)
562 boundpoints=[points[0].index, points[1].index]
565 y=min(plot.vectors[whatset][1][boundpoints[0]:boundpoints[1]])
567 print 'Chosen interval not valid. Try picking it again. Did you pick the same point as begin and end of interval?'
569 print 'Select point to measure'
570 points=self._measure_N_points(N=1, whatset=whatset)
571 #whatplot=points[0].dest
572 y=points[0].graph_coords[1]
574 #fixme: code duplication
575 boundaries=[self.basepoints[0].index, self.basepoints[1].index]
577 to_average=plot.vectors[whatset][1][boundaries[0]:boundaries[1]] #y points to average
579 avg=np.mean(to_average)
581 print str(forcebase*(10**12))+' pN'
582 to_dump='forcebase '+self.current.path+' '+str(forcebase*(10**12))+' pN'
583 self.outlet.push(to_dump)
585 def plotmanip_multiplier(self, plot, current):
587 Multiplies all the Y values of an SMFS curve by a value stored in the 'force_multiplier'
588 configuration variable. Useful for calibrations and other stuff.
592 if current.curve.experiment != 'smfs':
595 #only one set is present...
596 if len(self.plots[0].vectors) != 2:
600 if (self.config['force_multiplier']==1):
603 for i in range(len(plot.vectors[0][1])):
604 plot.vectors[0][1][i]=plot.vectors[0][1][i]*self.config['force_multiplier']
606 for i in range(len(plot.vectors[1][1])):
607 plot.vectors[1][1][i]=plot.vectors[1][1][i]*self.config['force_multiplier']
612 def plotmanip_flatten(self, plot, current, customvalue=False):
614 Subtracts a polynomial fit to the non-contact part of the curve, as to flatten it.
615 the best polynomial fit is chosen among polynomials of degree 1 to n, where n is
616 given by the configuration file or by the customvalue.
618 customvalue= int (>0) --> starts the function even if config says no (default=False)
622 if current.curve.experiment != 'smfs':
625 #only one set is present...
626 if len(self.plots[0].vectors) != 2:
629 #config is not flatten, and customvalue flag is false too
630 if (not self.config['flatten']) and (not customvalue):
637 max_cycles=customvalue
639 max_cycles=self.config['flatten'] #Using > 1 usually doesn't help and can give artefacts. However, it could be useful too.
641 contact_index=self.find_contact_point()
643 valn=[[] for item in range(max_exponent)]
644 yrn=[0.0 for item in range(max_exponent)]
645 errn=[0.0 for item in range(max_exponent)]
647 #Check if we have a proper numerical value
651 #Loudly and annoyingly complain if it's not a number, then fallback to zero
652 print '''Warning: flatten value is not a number!
653 Use "set flatten" or edit hooke.conf to set it properly
657 for i in range(int(max_cycles)):
659 x_ext=plot.vectors[0][0][contact_index+delta_contact:]
660 y_ext=plot.vectors[0][1][contact_index+delta_contact:]
661 x_ret=plot.vectors[1][0][contact_index+delta_contact:]
662 y_ret=plot.vectors[1][1][contact_index+delta_contact:]
663 for exponent in range(max_exponent):
665 valn[exponent]=sp.polyfit(x_ext,y_ext,exponent)
666 yrn[exponent]=sp.polyval(valn[exponent],x_ret)
667 errn[exponent]=sp.sqrt(sum((yrn[exponent]-y_ext)**2)/float(len(y_ext)))
669 print 'Cannot flatten!'
673 best_exponent=errn.index(min(errn))
676 ycorr_ext=y_ext-yrn[best_exponent]+y_ext[0] #noncontact part
677 yjoin_ext=np.array(plot.vectors[0][1][0:contact_index+delta_contact]) #contact part
679 ycorr_ret=y_ret-yrn[best_exponent]+y_ext[0] #noncontact part
680 yjoin_ret=np.array(plot.vectors[1][1][0:contact_index+delta_contact]) #contact part
682 ycorr_ext=np.concatenate((yjoin_ext, ycorr_ext))
683 ycorr_ret=np.concatenate((yjoin_ret, ycorr_ret))
685 plot.vectors[0][1]=list(ycorr_ext)
686 plot.vectors[1][1]=list(ycorr_ret)
691 def do_slope(self,args):
695 Measures the slope of a delimited chunk on the return trace.
696 The chunk can be delimited either by two manual clicks, or have
697 a fixed width, given as an argument.
699 Syntax: slope [width]
700 The facultative [width] parameter specifies how many
701 points will be considered for the fit. If [width] is
702 specified, only one click will be required.
703 (c) Marco Brucale, Massimo Sandal 2008
706 # Reads the facultative width argument
712 # Decides between the two forms of user input, as per (args)
714 # Gets the Xs of two clicked points as indexes on the current curve vector
715 print 'Click twice to delimit chunk'
716 points=self._measure_N_points(N=2,whatset=1)
718 print 'Click once on the leftmost point of the chunk (i.e.usually the peak)'
719 points=self._measure_N_points(N=1,whatset=1)
721 slope=self._slope(points,fitspan)
723 # Outputs the relevant slope parameter
726 to_dump='slope '+self.current.path+' '+str(slope)
727 self.outlet.push(to_dump)
729 def _slope(self,points,fitspan):
730 # Calls the function linefit_between
731 parameters=[0,0,[],[]]
733 clickedpoints=[points[0].index,points[1].index]
736 clickedpoints=[points[0].index-fitspan,points[0].index]
739 parameters=self.linefit_between(clickedpoints[0],clickedpoints[1])
741 print 'Cannot fit. Did you click twice the same point?'
744 # Outputs the relevant slope parameter
746 print str(parameters[0])
747 to_dump='slope '+self.curve.path+' '+str(parameters[0])
748 self.outlet.push(to_dump)
750 # Makes a vector with the fitted parameters and sends it to the GUI
751 xtoplot=parameters[2]
755 ytoplot.append((x*parameters[0])+parameters[1])
757 clickvector_x, clickvector_y=[], []
759 clickvector_x.append(item.graph_coords[0])
760 clickvector_y.append(item.graph_coords[1])
762 lineplot=self._get_displayed_plot(0) #get topmost displayed plot
764 lineplot.add_set(xtoplot,ytoplot)
765 lineplot.add_set(clickvector_x, clickvector_y)
768 if lineplot.styles==[]:
769 lineplot.styles=[None,None,None,'scatter']
771 lineplot.styles+=[None,'scatter']
772 if lineplot.colors==[]:
773 lineplot.colors=[None,None,'black',None]
775 lineplot.colors+=['black',None]
778 self._send_plot([lineplot])
783 def linefit_between(self,index1,index2,whatset=1):
785 Creates two vectors (xtofit,ytofit) slicing out from the
786 current return trace a portion delimited by the two indexes
788 Then does a least squares linear fit on that slice.
789 Finally returns [0]=the slope, [1]=the intercept of the
790 fitted 1st grade polynomial, and [2,3]=the actual (x,y) vectors
792 (c) Marco Brucale, Massimo Sandal 2008
794 # Translates the indexes into two vectors containing the x,y data to fit
795 xtofit=self.plots[0].vectors[whatset][0][index1:index2]
796 ytofit=self.plots[0].vectors[whatset][1][index1:index2]
798 # Does the actual linear fitting (simple least squares with numpy.polyfit)
800 linefit=np.polyfit(xtofit,ytofit,1)
802 return (linefit[0],linefit[1],xtofit,ytofit)
805 def fit_interval_nm(self,start_index,plot,nm,backwards):
807 Calculates the number of points to fit, given a fit interval in nm
808 start_index: index of point
810 backwards: if true, finds a point backwards.
812 whatset=1 #FIXME: should be decidable
813 x_vect=plot.vectors[1][0]
817 start=x_vect[start_index]
819 while abs(x_vect[i]-x_vect[start_index])*(10**9) < nm:
820 if i==0 or i==maxlen-1: #we reached boundaries of vector!
832 def find_current_peaks(self,noflatten, a=True, maxpeak=True):
835 a=self.convfilt_config['mindeviation']
839 print "Bad input, using default."
840 abs_devs=self.convfilt_config['mindeviation']
842 defplot=self.current.curve.default_plots()[0]
844 flatten=self._find_plotmanip('flatten') #Extract flatten plotmanip
845 defplot=flatten(defplot, self.current, customvalue=1) #Flatten curve before feeding it to has_peaks
846 pk_location,peak_size=self.has_peaks(defplot, abs_devs, maxpeak)
847 return pk_location, peak_size
850 def pickup_contact_point(self,N=1,whatset=1):
851 '''macro to pick up the contact point by clicking'''
852 contact_point=self._measure_N_points(N=1, whatset=1)[0]
853 contact_point_index=contact_point.index
854 self.wlccontact_point=contact_point
855 self.wlccontact_index=contact_point.index
856 self.wlccurrent=self.current.path
857 return contact_point, contact_point_index
860 def baseline_points(self,peak_location, displayed_plot):
861 clicks=self.config['baseline_clicks']
864 base_index_0=peak_location[-1]+self.fit_interval_nm(peak_location[-1], displayed_plot, self.config['auto_right_baseline'],False)
865 self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_0))
866 base_index_1=self.basepoints[0].index+self.fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'],False)
867 self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1))
869 print 'Select baseline'
871 self.basepoints=self._measure_N_points(N=1, whatset=1)
872 base_index_1=self.basepoints[0].index+self.fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'], False)
873 self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1))
875 self.basepoints=self._measure_N_points(N=2, whatset=1)
877 self.basecurrent=self.current.path
878 return self.basepoints