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
10 # modify it under the terms of the GNU Lesser General Public
11 # License as published by the Free Software Foundation, either
12 # version 3 of the License, or (at your option) any later version.
14 # Hooke is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU Lesser General 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 Builtin
37 from ..util.fit import PoorFit, ModelFitter
38 from .curve import CurveArgument
41 def scale(hooke, curve):
42 commands = hooke.commands
43 contact = [c for c in hooke.commands
44 if c.name == 'zero block surface contact point'][0]
45 force = [c for c in hooke.commands if c.name == 'add block force array'][0]
47 outqueue = NullQueue()
48 for i,block in enumerate(curve.data):
49 params = {'curve':curve, 'block':i}
50 contact._run(hooke, inqueue, outqueue, params)
51 force._run(hooke, inqueue, outqueue, params)
54 class SurfacePositionModel (ModelFitter):
57 The bilinear model is symmetric, but the parameter guessing and
58 sanity checks assume the contact region occurs for lower indicies
59 ("left of") the non-contact region. We also assume that
60 tip-surface attractions produce positive deflections.
64 Algorithm borrowed from WTK's `piezo package`_, specifically
65 from :func:`piezo.z_piezo_utils.analyzeSurfPosData`.
68 http://www.physics.drexel.edu/~wking/code/git/git.php?p=piezo.git
70 Fits the data to the bilinear :method:`model`.
72 In order for this model to produce a satisfactory fit, there
73 should be enough data in the off-surface region that interactions
74 due to proteins, etc. will not seriously skew the fit in the
80 def model(self, params):
81 """A continuous, bilinear model.
88 p_0 + p_1 x & \text{if $x <= p_2$}, \\
89 p_0 + p_1 p_2 + p_3 (x-p_2) & \text{if $x >= p_2$}.
92 Where :math:`p_0` is a vertical offset, :math:`p_1` is the slope
93 of the first region, :math:`p_2` is the transition location, and
94 :math:`p_3` is the slope of the second region.
96 p = params # convenient alias
97 if self.info.get('force zero non-contact slope', None) == True:
99 p.append(0.) # restore the non-contact slope parameter
100 r2 = numpy.round(abs(p[2]))
102 self._model_data[:r2] = p[0] + p[1] * numpy.arange(r2)
103 if r2 < len(self._data)-1:
104 self._model_data[r2:] = \
105 p[0] + p[1]*p[2] + p[3] * numpy.arange(len(self._data)-r2)
106 return self._model_data
108 def set_data(self, data, info=None):
109 super(SurfacePositionModel, self).set_data(data, info)
113 self.info['min position'] = 0
114 self.info['max position'] = len(data)
115 self.info['max deflection'] = data.max()
116 self.info['min deflection'] = data.min()
117 self.info['position range'] = self.info['max position'] - self.info['min position']
118 self.info['deflection range'] = self.info['max deflection'] - self.info['min deflection']
120 def guess_initial_params(self, outqueue=None):
121 """Guess the initial parameters.
125 We guess initial parameters such that the offset (:math:`p_1`)
126 matches the minimum deflection, the kink (:math:`p_2`) occurs in
127 the middle of the data, the initial (contact) slope (:math:`p_0`)
128 produces the maximum deflection at the left-most point, and the
129 final (non-contact) slope (:math:`p_3`) is zero.
131 left_offset = self.info['min deflection']
132 left_slope = 2*(self.info['deflection range']
133 /self.info['position range'])
134 kink_position = (self.info['max position']
135 +self.info['min position'])/2.0
137 self.info['guessed contact slope'] = left_slope
138 params = [left_offset, left_slope, kink_position, right_slope]
139 if self.info.get('force zero non-contact slope', None) == True:
143 def guess_scale(self, params, outqueue=None):
144 """Guess the parameter scales.
149 We guess offset scale (:math:`p_0`) as one tenth of the total
150 deflection range, the kink scale (:math:`p_2`) as one tenth of
151 the total index range, the initial (contact) slope scale
152 (:math:`p_1`) as one tenth of the contact slope estimation,
153 and the final (non-contact) slope scale (:math:`p_3`) is as
154 one tenth of the initial slope scale.
156 offset_scale = self.info['deflection range']/10.
157 left_slope_scale = abs(params[1])/10.
158 kink_scale = self.info['position range']/10.
159 right_slope_scale = left_slope_scale/10.
160 scale = [offset_scale, left_slope_scale, kink_scale, right_slope_scale]
161 if self.info.get('force zero non-contact slope', None) == True:
165 def fit(self, *args, **kwargs):
166 self.info['guessed contact slope'] = None
167 params = super(SurfacePositionModel, self).fit(*args, **kwargs)
168 params[2] = abs(params[2])
169 if self.info.get('force zero non-contact slope', None) == True:
170 params = list(params)
171 params.append(0.) # restore the non-contact slope parameter
173 # check that the fit is reasonable, see the :meth:`model` docstring
174 # for parameter descriptions. HACK: hardcoded cutoffs.
175 if abs(params[3]*10) > abs(params[1]) :
176 raise PoorFit('Slope in non-contact region, or no slope in contact')
177 if params[2] < self.info['min position']+0.02*self.info['position range']:
179 'No kink (kink %g less than %g, need more space to left)'
181 self.info['min position']+0.02*self.info['position range']))
182 if params[2] > self.info['max position']-0.02*self.info['position range']:
184 'No kink (kink %g more than %g, need more space to right)'
186 self.info['max position']-0.02*self.info['position range']))
187 if (self.info['guessed contact slope'] != None
188 and abs(params[1]) < 0.5 * abs(self.info['guessed contact slope'])):
189 raise PoorFit('Too far (contact slope %g, but expected ~%g'
190 % (params[3], self.info['guessed contact slope']))
193 class VelocityClampPlugin (Builtin):
195 super(VelocityClampPlugin, self).__init__(name='vclamp')
197 SurfaceContactCommand(self), ForceCommand(self),
200 def default_settings(self):
202 Setting(section=self.setting_section, help=self.__doc__),
203 Setting(section=self.setting_section,
204 option='surface contact point algorithm',
206 help='Select the surface contact point algorithm. See the documentation for descriptions of available algorithms.')
210 class SurfaceContactCommand (Command):
211 """Automatically determine a block's surface contact point.
213 Uses the block's `z piezo (m)` and `deflection (m)` arrays.
214 Stores the contact parameters in `block.info`'s `surface distance
215 offset (m)` and `surface deflection offset (m)`. Model-specific
216 fitting information is stored in `surface detection parameters`.
218 The adjusted data columns `surface distance (m)` and `surface
219 adjusted deflection (m)` are also added to the block.
221 You can select the contact point algorithm with the creatively
222 named `surface contact point algorithm` configuration setting.
223 Currently available options are:
225 * fmms (:meth:`find_contact_point_fmms`)
226 * ms (:meth:`find_contact_point_ms`)
227 * wtk (:meth:`find_contact_point_wtk`)
229 def __init__(self, plugin):
230 super(SurfaceContactCommand, self).__init__(
231 name='zero block surface contact point',
234 Argument(name='block', aliases=['set'], type='int', default=0,
236 Data block for which the force should be calculated. For an
237 approach/retract force curve, `0` selects the approaching curve and `1`
238 selects the retracting curve.
241 help=self.__doc__, plugin=plugin)
243 def _run(self, hooke, inqueue, outqueue, params):
244 data = params['curve'].data[int(params['block'])] # HACK, int() should be handled by ui
245 # HACK? rely on params['curve'] being bound to the local hooke
246 # playlist (i.e. not a copy, as you would get by passing a
247 # curve through the queue). Ugh. Stupid queues. As an
248 # alternative, we could pass lookup information through the
250 new = Data((data.shape[0], data.shape[1]+2), dtype=data.dtype)
251 new.info = copy.deepcopy(data.info)
253 new.info['columns'].extend(
254 ['surface distance (m)', 'surface adjusted deflection (m)'])
255 z_data = data[:,data.info['columns'].index('z piezo (m)')]
256 d_data = data[:,data.info['columns'].index('deflection (m)')]
257 i,deflection_offset,ps = self.find_contact_point(
258 params['curve'], z_data, d_data, outqueue)
259 surface_offset = z_data[i]
260 new.info['surface distance offset (m)'] = surface_offset
261 new.info['surface deflection offset (m)'] = deflection_offset
262 new.info['surface detection parameters'] = ps
263 new[:,-2] = z_data - surface_offset
264 new[:,-1] = d_data - deflection_offset
265 data = params['curve'].data[int(params['block'])] # HACK, int() should be handled by ui
266 params['curve'].data[int(params['block'])] = new # HACK, int() should be handled by ui
268 def find_contact_point(self, curve, z_data, d_data, outqueue=None):
269 """Railyard for the `find_contact_point_*` family.
271 Uses the `surface contact point algorithm` configuration
272 setting to call the appropriate backend algorithm.
274 fn = getattr(self, 'find_contact_point_%s'
275 % self.plugin.config['surface contact point algorithm'])
276 return fn(curve, z_data, d_data, outqueue)
278 def find_contact_point_fmms(self, curve, z_data, d_data, outqueue=None):
279 """Algorithm by Francesco Musiani and Massimo Sandal.
285 0) Driver-specific workarounds, e.g. deal with the PicoForce
286 trigger bug by excluding retraction portions with excessive
288 1) Select the second half (non-contact side) of the retraction
290 2) Fit the selection to a line.
291 3) If the fit is not almost horizontal, halve the selection
293 4) Average the selection and use it as a baseline.
294 5) Slide in from the start (contact side) of the retraction
295 curve, until you find a point with greater than baseline
296 deflection. That point is the contact point.
298 if curve.info['filetype'] == 'picoforce':
299 # Take care of the picoforce trigger bug (TODO: example
300 # data file demonstrating the bug). We exclude portions
301 # of the curve that have too much standard deviation.
302 # Yes, a lot of magic is here.
303 check_start = len(d_data)-len(d_data)/20
304 monster_start = len(d_data)
306 # look at the non-contact tail
307 non_monster = d_data[check_start:monster_start]
308 if non_monster.std() < 2e-10: # HACK: hardcoded cutoff
310 else: # move further away from the monster
311 check_start -= len(d_data)/50
312 monster_start -= len(d_data)/50
313 z_data = z_data[:monster_start]
314 d_data = d_data[:monster_start]
316 # take half of the thing to start
317 selection_start = len(d_data)/2
319 z_chunk = z_data[selection_start:]
320 d_chunk = d_data[selection_start:]
321 slope,intercept,r,two_tailed_prob,stderr_of_the_estimate = \
322 scipy.stats.linregress(z_chunk, d_chunk)
323 # We stop if we found an almost-horizontal fit or if we're
324 # getting to small a selection. FIXME: 0.1 and 5./6 here
325 # are "magic numbers" (although reasonable)
326 if (abs(slope) < 0.1 # deflection (m) / surface (m)
327 or selection_start > 5./6*len(d_data)):
329 selection_start += 10
331 d_baseline = d_chunk.mean()
333 # find the first point above the calculated baseline
335 while i < len(d_data) and d_data[i] < ymean:
337 return (i, d_baseline, {})
339 def find_contact_point_ms(self, curve, z_data, d_data, outqueue=None):
340 """Algorithm by Massimo Sandal.
344 WTK: At least the commits are by Massimo, and I see no notes
345 attributing the algorithm to anyone else.
351 xext=raw_plot.vectors[0][0]
352 yext=raw_plot.vectors[0][1]
353 xret2=raw_plot.vectors[1][0]
354 yret=raw_plot.vectors[1][1]
356 first_point=[xext[0], yext[0]]
357 last_point=[xext[-1], yext[-1]]
359 #regr=scipy.polyfit(first_point, last_point,1)[0:2]
360 diffx=abs(first_point[0]-last_point[0])
361 diffy=abs(first_point[1]-last_point[1])
363 #using polyfit results in numerical errors. good old algebra.
365 b=first_point[1]-(a*first_point[0])
366 baseline=scipy.polyval((a,b), xext)
368 ysub=[item-basitem for item,basitem in zip(yext,baseline)]
370 contact=ysub.index(min(ysub))
372 return xext,ysub,contact
374 #now, exploit a ClickedPoint instance to calculate index...
376 dummy.absolute_coords=(x_intercept,y_intercept)
377 dummy.find_graph_coords(xret2,yret)
380 return dummy.index, regr, regr_contact
384 def find_contact_point_wtk(self, curve, z_data, d_data, outqueue=None):
385 """Algorithm by W. Trevor King.
389 Uses :func:`analyze_surf_pos_data_wtk` internally.
391 reverse = z_data[0] > z_data[-1]
392 if reverse == True: # approaching, contact region on the right
393 d_data = d_data[::-1]
394 s = SurfacePositionModel(d_data)
395 s.info['force zero non-contact slope'] = True
396 offset,contact_slope,surface_index,non_contact_slope = s.fit(
400 'contact slope': contact_slope,
401 'surface index': surface_index,
402 'non-contact slope': non_contact_slope,
405 deflection_offset = offset + contact_slope*surface_index,
407 surface_index = len(d_data)-1-surface_index
408 return (numpy.round(surface_index), deflection_offset, info)
410 class ForceCommand (Command):
411 """Calculate a block's `deflection (N)` array.
413 Uses the block's `deflection (m)` array and `spring constant
416 def __init__(self, plugin):
417 super(ForceCommand, self).__init__(
418 name='add block force array',
421 Argument(name='block', aliases=['set'], type='int', default=0,
423 Data block for which the force should be calculated. For an
424 approach/retract force curve, `0` selects the approaching curve and `1`
425 selects the retracting curve.
428 help=self.__doc__, plugin=plugin)
430 def _run(self, hooke, inqueue, outqueue, params):
431 data = params['curve'].data[int(params['block'])] # HACK, int() should be handled by ui
432 # HACK? rely on params['curve'] being bound to the local hooke
433 # playlist (i.e. not a copy, as you would get by passing a
434 # curve through the queue). Ugh. Stupid queues. As an
435 # alternative, we could pass lookup information through the
437 new = Data((data.shape[0], data.shape[1]+1), dtype=data.dtype)
438 new.info = copy.deepcopy(data.info)
440 new.info['columns'].append('deflection (N)')
441 d_data = data[:,data.info['columns'].index('surface adjusted deflection (m)')]
442 new[:,-1] = d_data * data.info['spring constant (N/m)']
443 params['curve'].data[int(params['block'])] = new # HACK, int() should be handled by ui
446 class generalvclampCommands(object):
448 def do_subtplot(self, args):
451 (procplots.py plugin)
452 Plots the difference between ret and ext current curve
456 #FIXME: sub_filter and sub_order must be args
458 if len(self.plots[0].vectors) != 2:
459 print 'This command only works on a curve with two different plots.'
462 outplot=self.subtract_curves(sub_order=1)
464 plot_graph=self.list_of_events['plot_graph']
465 wx.PostEvent(self.frame,plot_graph(plots=[outplot]))
467 def _plug_init(self):
468 self.basecurrent=None
472 def do_distance(self,args):
476 Measure the distance (in nm) between two points.
477 For a standard experiment this is the delta X distance.
478 For a force clamp experiment this is the delta Y distance (actually becomes
483 if self.current.curve.experiment == 'clamp':
484 print 'You wanted to use zpiezo perhaps?'
487 dx,unitx,dy,unity=self._delta(set=1)
488 print str(dx*(10**9))+' nm'
489 to_dump='distance '+self.current.path+' '+str(dx*(10**9))+' nm'
490 self.outlet.push(to_dump)
493 def do_force(self,args):
497 Measure the force difference (in pN) between two points
501 if self.current.curve.experiment == 'clamp':
502 print 'This command makes no sense for a force clamp experiment.'
504 dx,unitx,dy,unity=self._delta(set=1)
505 print str(dy*(10**12))+' pN'
506 to_dump='force '+self.current.path+' '+str(dy*(10**12))+' pN'
507 self.outlet.push(to_dump)
510 def do_forcebase(self,args):
514 Measures the difference in force (in pN) between a point and a baseline
515 took as the average between two points.
517 The baseline is fixed once for a given curve and different force measurements,
518 unless the user wants it to be recalculated
520 Syntax: forcebase [rebase]
521 rebase: Forces forcebase to ask again the baseline
522 max: Instead of asking for a point to measure, asks for two points and use
523 the maximum peak in between
525 rebase=False #if true=we select rebase
526 maxpoint=False #if true=we measure the maximum peak
528 plot=self._get_displayed_plot()
529 whatset=1 #fixme: for all sets
530 if 'rebase' in args or (self.basecurrent != self.current.path):
536 print 'Select baseline'
537 self.basepoints=self._measure_N_points(N=2, whatset=whatset)
538 self.basecurrent=self.current.path
541 print 'Select two points'
542 points=self._measure_N_points(N=2, whatset=whatset)
543 boundpoints=[points[0].index, points[1].index]
546 y=min(plot.vectors[whatset][1][boundpoints[0]:boundpoints[1]])
548 print 'Chosen interval not valid. Try picking it again. Did you pick the same point as begin and end of interval?'
550 print 'Select point to measure'
551 points=self._measure_N_points(N=1, whatset=whatset)
552 #whatplot=points[0].dest
553 y=points[0].graph_coords[1]
555 #fixme: code duplication
556 boundaries=[self.basepoints[0].index, self.basepoints[1].index]
558 to_average=plot.vectors[whatset][1][boundaries[0]:boundaries[1]] #y points to average
560 avg=np.mean(to_average)
562 print str(forcebase*(10**12))+' pN'
563 to_dump='forcebase '+self.current.path+' '+str(forcebase*(10**12))+' pN'
564 self.outlet.push(to_dump)
566 def plotmanip_multiplier(self, plot, current):
568 Multiplies all the Y values of an SMFS curve by a value stored in the 'force_multiplier'
569 configuration variable. Useful for calibrations and other stuff.
573 if current.curve.experiment != 'smfs':
576 #only one set is present...
577 if len(self.plots[0].vectors) != 2:
581 if (self.config['force_multiplier']==1):
584 for i in range(len(plot.vectors[0][1])):
585 plot.vectors[0][1][i]=plot.vectors[0][1][i]*self.config['force_multiplier']
587 for i in range(len(plot.vectors[1][1])):
588 plot.vectors[1][1][i]=plot.vectors[1][1][i]*self.config['force_multiplier']
593 def plotmanip_flatten(self, plot, current, customvalue=False):
595 Subtracts a polynomial fit to the non-contact part of the curve, as to flatten it.
596 the best polynomial fit is chosen among polynomials of degree 1 to n, where n is
597 given by the configuration file or by the customvalue.
599 customvalue= int (>0) --> starts the function even if config says no (default=False)
603 if current.curve.experiment != 'smfs':
606 #only one set is present...
607 if len(self.plots[0].vectors) != 2:
610 #config is not flatten, and customvalue flag is false too
611 if (not self.config['flatten']) and (not customvalue):
618 max_cycles=customvalue
620 max_cycles=self.config['flatten'] #Using > 1 usually doesn't help and can give artefacts. However, it could be useful too.
622 contact_index=self.find_contact_point()
624 valn=[[] for item in range(max_exponent)]
625 yrn=[0.0 for item in range(max_exponent)]
626 errn=[0.0 for item in range(max_exponent)]
628 #Check if we have a proper numerical value
632 #Loudly and annoyingly complain if it's not a number, then fallback to zero
633 print '''Warning: flatten value is not a number!
634 Use "set flatten" or edit hooke.conf to set it properly
638 for i in range(int(max_cycles)):
640 x_ext=plot.vectors[0][0][contact_index+delta_contact:]
641 y_ext=plot.vectors[0][1][contact_index+delta_contact:]
642 x_ret=plot.vectors[1][0][contact_index+delta_contact:]
643 y_ret=plot.vectors[1][1][contact_index+delta_contact:]
644 for exponent in range(max_exponent):
646 valn[exponent]=sp.polyfit(x_ext,y_ext,exponent)
647 yrn[exponent]=sp.polyval(valn[exponent],x_ret)
648 errn[exponent]=sp.sqrt(sum((yrn[exponent]-y_ext)**2)/float(len(y_ext)))
650 print 'Cannot flatten!'
654 best_exponent=errn.index(min(errn))
657 ycorr_ext=y_ext-yrn[best_exponent]+y_ext[0] #noncontact part
658 yjoin_ext=np.array(plot.vectors[0][1][0:contact_index+delta_contact]) #contact part
660 ycorr_ret=y_ret-yrn[best_exponent]+y_ext[0] #noncontact part
661 yjoin_ret=np.array(plot.vectors[1][1][0:contact_index+delta_contact]) #contact part
663 ycorr_ext=np.concatenate((yjoin_ext, ycorr_ext))
664 ycorr_ret=np.concatenate((yjoin_ret, ycorr_ret))
666 plot.vectors[0][1]=list(ycorr_ext)
667 plot.vectors[1][1]=list(ycorr_ret)
672 def do_slope(self,args):
676 Measures the slope of a delimited chunk on the return trace.
677 The chunk can be delimited either by two manual clicks, or have
678 a fixed width, given as an argument.
680 Syntax: slope [width]
681 The facultative [width] parameter specifies how many
682 points will be considered for the fit. If [width] is
683 specified, only one click will be required.
684 (c) Marco Brucale, Massimo Sandal 2008
687 # Reads the facultative width argument
693 # Decides between the two forms of user input, as per (args)
695 # Gets the Xs of two clicked points as indexes on the current curve vector
696 print 'Click twice to delimit chunk'
697 points=self._measure_N_points(N=2,whatset=1)
699 print 'Click once on the leftmost point of the chunk (i.e.usually the peak)'
700 points=self._measure_N_points(N=1,whatset=1)
702 slope=self._slope(points,fitspan)
704 # Outputs the relevant slope parameter
707 to_dump='slope '+self.current.path+' '+str(slope)
708 self.outlet.push(to_dump)
710 def _slope(self,points,fitspan):
711 # Calls the function linefit_between
712 parameters=[0,0,[],[]]
714 clickedpoints=[points[0].index,points[1].index]
717 clickedpoints=[points[0].index-fitspan,points[0].index]
720 parameters=self.linefit_between(clickedpoints[0],clickedpoints[1])
722 print 'Cannot fit. Did you click twice the same point?'
725 # Outputs the relevant slope parameter
727 print str(parameters[0])
728 to_dump='slope '+self.curve.path+' '+str(parameters[0])
729 self.outlet.push(to_dump)
731 # Makes a vector with the fitted parameters and sends it to the GUI
732 xtoplot=parameters[2]
736 ytoplot.append((x*parameters[0])+parameters[1])
738 clickvector_x, clickvector_y=[], []
740 clickvector_x.append(item.graph_coords[0])
741 clickvector_y.append(item.graph_coords[1])
743 lineplot=self._get_displayed_plot(0) #get topmost displayed plot
745 lineplot.add_set(xtoplot,ytoplot)
746 lineplot.add_set(clickvector_x, clickvector_y)
749 if lineplot.styles==[]:
750 lineplot.styles=[None,None,None,'scatter']
752 lineplot.styles+=[None,'scatter']
753 if lineplot.colors==[]:
754 lineplot.colors=[None,None,'black',None]
756 lineplot.colors+=['black',None]
759 self._send_plot([lineplot])
764 def linefit_between(self,index1,index2,whatset=1):
766 Creates two vectors (xtofit,ytofit) slicing out from the
767 current return trace a portion delimited by the two indexes
769 Then does a least squares linear fit on that slice.
770 Finally returns [0]=the slope, [1]=the intercept of the
771 fitted 1st grade polynomial, and [2,3]=the actual (x,y) vectors
773 (c) Marco Brucale, Massimo Sandal 2008
775 # Translates the indexes into two vectors containing the x,y data to fit
776 xtofit=self.plots[0].vectors[whatset][0][index1:index2]
777 ytofit=self.plots[0].vectors[whatset][1][index1:index2]
779 # Does the actual linear fitting (simple least squares with numpy.polyfit)
781 linefit=np.polyfit(xtofit,ytofit,1)
783 return (linefit[0],linefit[1],xtofit,ytofit)
786 def fit_interval_nm(self,start_index,plot,nm,backwards):
788 Calculates the number of points to fit, given a fit interval in nm
789 start_index: index of point
791 backwards: if true, finds a point backwards.
793 whatset=1 #FIXME: should be decidable
794 x_vect=plot.vectors[1][0]
798 start=x_vect[start_index]
800 while abs(x_vect[i]-x_vect[start_index])*(10**9) < nm:
801 if i==0 or i==maxlen-1: #we reached boundaries of vector!
813 def find_current_peaks(self,noflatten, a=True, maxpeak=True):
816 a=self.convfilt_config['mindeviation']
820 print "Bad input, using default."
821 abs_devs=self.convfilt_config['mindeviation']
823 defplot=self.current.curve.default_plots()[0]
825 flatten=self._find_plotmanip('flatten') #Extract flatten plotmanip
826 defplot=flatten(defplot, self.current, customvalue=1) #Flatten curve before feeding it to has_peaks
827 pk_location,peak_size=self.has_peaks(defplot, abs_devs, maxpeak)
828 return pk_location, peak_size
831 def pickup_contact_point(self,N=1,whatset=1):
832 '''macro to pick up the contact point by clicking'''
833 contact_point=self._measure_N_points(N=1, whatset=1)[0]
834 contact_point_index=contact_point.index
835 self.wlccontact_point=contact_point
836 self.wlccontact_index=contact_point.index
837 self.wlccurrent=self.current.path
838 return contact_point, contact_point_index
841 def baseline_points(self,peak_location, displayed_plot):
842 clicks=self.config['baseline_clicks']
845 base_index_0=peak_location[-1]+self.fit_interval_nm(peak_location[-1], displayed_plot, self.config['auto_right_baseline'],False)
846 self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_0))
847 base_index_1=self.basepoints[0].index+self.fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'],False)
848 self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1))
850 print 'Select baseline'
852 self.basepoints=self._measure_N_points(N=1, whatset=1)
853 base_index_1=self.basepoints[0].index+self.fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'], False)
854 self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1))
856 self.basepoints=self._measure_N_points(N=2, whatset=1)
858 self.basecurrent=self.current.path
859 return self.basepoints