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(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 if self.info.get('force zero non-contact slope', None) == True:
169 params = list(params)
170 params.append(0.) # restore the non-contact slope parameter
172 # check that the fit is reasonable, see the :meth:`model` docstring
173 # for parameter descriptions. HACK: hardcoded cutoffs.
174 if abs(params[3]*10) > abs(params[1]) :
175 raise PoorFit('Slope in non-contact region, or no slope in contact')
176 if params[2] < self.info['min position']+0.02*self.info['position range']:
178 'No kink (kink %g less than %g, need more space to left)'
180 self.info['min position']+0.02*self.info['position range']))
181 if params[2] > self.info['max position']-0.02*self.info['position range']:
183 'No kink (kink %g more than %g, need more space to right)'
185 self.info['max position']-0.02*self.info['position range']))
186 if (self.info['guessed contact slope'] != None
187 and abs(params[1]) < 0.5 * abs(self.info['guessed contact slope'])):
188 raise PoorFit('Too far (contact slope %g, but expected ~%g'
189 % (params[3], self.info['guessed contact slope']))
192 class VelocityClampPlugin (Builtin):
194 super(VelocityClampPlugin, self).__init__(name='vclamp')
196 SurfaceContactCommand(self), ForceCommand(self),
199 def default_settings(self):
201 Setting(section=self.setting_section, help=self.__doc__),
202 Setting(section=self.setting_section,
203 option='surface contact point algorithm',
205 help='Select the surface contact point algorithm. See the documentation for descriptions of available algorithms.')
209 class SurfaceContactCommand (Command):
210 """Automatically determine a block's surface contact point.
212 Uses the block's `z piezo (m)` and `deflection (m)` arrays.
213 Stores the contact parameters in `block.info`'s `surface distance
214 offset (m)` and `surface deflection offset (m)`. Model-specific
215 fitting information is stored in `surface detection parameters`.
217 The adjusted data columns `surface distance (m)` and `surface
218 adjusted deflection (m)` are also added to the block.
220 You can select the contact point algorithm with the creatively
221 named `surface contact point algorithm` configuration setting.
222 Currently available options are:
224 * fmms (:meth:`find_contact_point_fmms`)
225 * ms (:meth:`find_contact_point_ms`)
226 * wtk (:meth:`find_contact_point_wtk`)
228 def __init__(self, plugin):
229 super(SurfaceContactCommand, self).__init__(
230 name='zero block surface contact point',
233 Argument(name='block', aliases=['set'], type='int', default=0,
235 Data block for which the force should be calculated. For an
236 approach/retract force curve, `0` selects the approaching curve and `1`
237 selects the retracting curve.
240 help=self.__doc__, plugin=plugin)
242 def _run(self, hooke, inqueue, outqueue, params):
243 data = params['curve'].data[int(params['block'])] # HACK, int() should be handled by ui
244 # HACK? rely on params['curve'] being bound to the local hooke
245 # playlist (i.e. not a copy, as you would get by passing a
246 # curve through the queue). Ugh. Stupid queues. As an
247 # alternative, we could pass lookup information through the
249 new = Data((data.shape[0], data.shape[1]+2), dtype=data.dtype)
250 new.info = copy.deepcopy(data.info)
252 new.info['columns'].extend(
253 ['surface distance (m)', 'surface adjusted deflection (m)'])
254 z_data = data[:,data.info['columns'].index('z piezo (m)')]
255 d_data = data[:,data.info['columns'].index('deflection (m)')]
256 i,deflection_offset,ps = self.find_contact_point(
257 params['curve'], z_data, d_data, outqueue)
258 surface_offset = z_data[i]
259 new.info['surface distance offset (m)'] = surface_offset
260 new.info['surface deflection offset (m)'] = deflection_offset
261 new.info['surface detection parameters'] = ps
262 new[:,-2] = z_data - surface_offset
263 new[:,-1] = d_data - deflection_offset
264 data = params['curve'].data[int(params['block'])] # HACK, int() should be handled by ui
265 params['curve'].data[int(params['block'])] = new # HACK, int() should be handled by ui
267 def find_contact_point(self, curve, z_data, d_data, outqueue=None):
268 """Railyard for the `find_contact_point_*` family.
270 Uses the `surface contact point algorithm` configuration
271 setting to call the appropriate backend algorithm.
273 fn = getattr(self, 'find_contact_point_%s'
274 % self.plugin.config['surface contact point algorithm'])
275 return fn(curve, z_data, d_data, outqueue)
277 def find_contact_point_fmms(self, curve, z_data, d_data, outqueue=None):
278 """Algorithm by Francesco Musiani and Massimo Sandal.
284 0) Driver-specific workarounds, e.g. deal with the PicoForce
285 trigger bug by excluding retraction portions with excessive
287 1) Select the second half (non-contact side) of the retraction
289 2) Fit the selection to a line.
290 3) If the fit is not almost horizontal, halve the selection
292 4) Average the selection and use it as a baseline.
293 5) Slide in from the start (contact side) of the retraction
294 curve, until you find a point with greater than baseline
295 deflection. That point is the contact point.
297 if curve.info['filetype'] == 'picoforce':
298 # Take care of the picoforce trigger bug (TODO: example
299 # data file demonstrating the bug). We exclude portions
300 # of the curve that have too much standard deviation.
301 # Yes, a lot of magic is here.
302 check_start = len(d_data)-len(d_data)/20
303 monster_start = len(d_data)
305 # look at the non-contact tail
306 non_monster = d_data[check_start:monster_start]
307 if non_monster.std() < 2e-10: # HACK: hardcoded cutoff
309 else: # move further away from the monster
310 check_start -= len(d_data)/50
311 monster_start -= len(d_data)/50
312 z_data = z_data[:monster_start]
313 d_data = d_data[:monster_start]
315 # take half of the thing to start
316 selection_start = len(d_data)/2
318 z_chunk = z_data[selection_start:]
319 d_chunk = d_data[selection_start:]
320 slope,intercept,r,two_tailed_prob,stderr_of_the_estimate = \
321 scipy.stats.linregress(z_chunk, d_chunk)
322 # We stop if we found an almost-horizontal fit or if we're
323 # getting to small a selection. FIXME: 0.1 and 5./6 here
324 # are "magic numbers" (although reasonable)
325 if (abs(slope) < 0.1 # deflection (m) / surface (m)
326 or selection_start > 5./6*len(d_data)):
328 selection_start += 10
330 d_baseline = d_chunk.mean()
332 # find the first point above the calculated baseline
334 while i < len(d_data) and d_data[i] < ymean:
336 return (i, d_baseline, {})
338 def find_contact_point_ms(self, curve, z_data, d_data, outqueue=None):
339 """Algorithm by Massimo Sandal.
343 WTK: At least the commits are by Massimo, and I see no notes
344 attributing the algorithm to anyone else.
350 xext=raw_plot.vectors[0][0]
351 yext=raw_plot.vectors[0][1]
352 xret2=raw_plot.vectors[1][0]
353 yret=raw_plot.vectors[1][1]
355 first_point=[xext[0], yext[0]]
356 last_point=[xext[-1], yext[-1]]
358 #regr=scipy.polyfit(first_point, last_point,1)[0:2]
359 diffx=abs(first_point[0]-last_point[0])
360 diffy=abs(first_point[1]-last_point[1])
362 #using polyfit results in numerical errors. good old algebra.
364 b=first_point[1]-(a*first_point[0])
365 baseline=scipy.polyval((a,b), xext)
367 ysub=[item-basitem for item,basitem in zip(yext,baseline)]
369 contact=ysub.index(min(ysub))
371 return xext,ysub,contact
373 #now, exploit a ClickedPoint instance to calculate index...
375 dummy.absolute_coords=(x_intercept,y_intercept)
376 dummy.find_graph_coords(xret2,yret)
379 return dummy.index, regr, regr_contact
383 def find_contact_point_wtk(self, curve, z_data, d_data, outqueue=None):
384 """Algorithm by W. Trevor King.
388 Uses :func:`analyze_surf_pos_data_wtk` internally.
390 reverse = z_data[0] > z_data[-1]
391 if reverse == True: # approaching, contact region on the right
392 d_data = d_data[::-1]
393 s = SurfacePositionModel(d_data)
394 s.info['force zero non-contact slope'] = True
395 offset,contact_slope,surface_index,non_contact_slope = s.fit(
399 'contact slope': contact_slope,
400 'surface index': surface_index,
401 'non-contact slope': non_contact_slope,
404 deflection_offset = offset + contact_slope*surface_index,
406 surface_index = len(d_data)-1-surface_index
407 return (numpy.round(surface_index), deflection_offset, info)
409 class ForceCommand (Command):
410 """Calculate a block's `deflection (N)` array.
412 Uses the block's `deflection (m)` array and `spring constant
415 def __init__(self, plugin):
416 super(ForceCommand, self).__init__(
417 name='add block force array',
420 Argument(name='block', aliases=['set'], type='int', default=0,
422 Data block for which the force should be calculated. For an
423 approach/retract force curve, `0` selects the approaching curve and `1`
424 selects the retracting curve.
427 help=self.__doc__, plugin=plugin)
429 def _run(self, hooke, inqueue, outqueue, params):
430 data = params['curve'].data[int(params['block'])] # HACK, int() should be handled by ui
431 # HACK? rely on params['curve'] being bound to the local hooke
432 # playlist (i.e. not a copy, as you would get by passing a
433 # curve through the queue). Ugh. Stupid queues. As an
434 # alternative, we could pass lookup information through the
436 new = Data((data.shape[0], data.shape[1]+1), dtype=data.dtype)
437 new.info = copy.deepcopy(data.info)
439 new.info['columns'].append('deflection (N)')
440 d_data = data[:,data.info['columns'].index('surface adjusted deflection (m)')]
441 new[:,-1] = d_data * data.info['spring constant (N/m)']
442 params['curve'].data[int(params['block'])] = new # HACK, int() should be handled by ui
445 class generalvclampCommands(object):
447 def do_subtplot(self, args):
450 (procplots.py plugin)
451 Plots the difference between ret and ext current curve
455 #FIXME: sub_filter and sub_order must be args
457 if len(self.plots[0].vectors) != 2:
458 print 'This command only works on a curve with two different plots.'
461 outplot=self.subtract_curves(sub_order=1)
463 plot_graph=self.list_of_events['plot_graph']
464 wx.PostEvent(self.frame,plot_graph(plots=[outplot]))
466 def _plug_init(self):
467 self.basecurrent=None
471 def do_distance(self,args):
475 Measure the distance (in nm) between two points.
476 For a standard experiment this is the delta X distance.
477 For a force clamp experiment this is the delta Y distance (actually becomes
482 if self.current.curve.experiment == 'clamp':
483 print 'You wanted to use zpiezo perhaps?'
486 dx,unitx,dy,unity=self._delta(set=1)
487 print str(dx*(10**9))+' nm'
488 to_dump='distance '+self.current.path+' '+str(dx*(10**9))+' nm'
489 self.outlet.push(to_dump)
492 def do_force(self,args):
496 Measure the force difference (in pN) between two points
500 if self.current.curve.experiment == 'clamp':
501 print 'This command makes no sense for a force clamp experiment.'
503 dx,unitx,dy,unity=self._delta(set=1)
504 print str(dy*(10**12))+' pN'
505 to_dump='force '+self.current.path+' '+str(dy*(10**12))+' pN'
506 self.outlet.push(to_dump)
509 def do_forcebase(self,args):
513 Measures the difference in force (in pN) between a point and a baseline
514 took as the average between two points.
516 The baseline is fixed once for a given curve and different force measurements,
517 unless the user wants it to be recalculated
519 Syntax: forcebase [rebase]
520 rebase: Forces forcebase to ask again the baseline
521 max: Instead of asking for a point to measure, asks for two points and use
522 the maximum peak in between
524 rebase=False #if true=we select rebase
525 maxpoint=False #if true=we measure the maximum peak
527 plot=self._get_displayed_plot()
528 whatset=1 #fixme: for all sets
529 if 'rebase' in args or (self.basecurrent != self.current.path):
535 print 'Select baseline'
536 self.basepoints=self._measure_N_points(N=2, whatset=whatset)
537 self.basecurrent=self.current.path
540 print 'Select two points'
541 points=self._measure_N_points(N=2, whatset=whatset)
542 boundpoints=[points[0].index, points[1].index]
545 y=min(plot.vectors[whatset][1][boundpoints[0]:boundpoints[1]])
547 print 'Chosen interval not valid. Try picking it again. Did you pick the same point as begin and end of interval?'
549 print 'Select point to measure'
550 points=self._measure_N_points(N=1, whatset=whatset)
551 #whatplot=points[0].dest
552 y=points[0].graph_coords[1]
554 #fixme: code duplication
555 boundaries=[self.basepoints[0].index, self.basepoints[1].index]
557 to_average=plot.vectors[whatset][1][boundaries[0]:boundaries[1]] #y points to average
559 avg=np.mean(to_average)
561 print str(forcebase*(10**12))+' pN'
562 to_dump='forcebase '+self.current.path+' '+str(forcebase*(10**12))+' pN'
563 self.outlet.push(to_dump)
565 def plotmanip_multiplier(self, plot, current):
567 Multiplies all the Y values of an SMFS curve by a value stored in the 'force_multiplier'
568 configuration variable. Useful for calibrations and other stuff.
572 if current.curve.experiment != 'smfs':
575 #only one set is present...
576 if len(self.plots[0].vectors) != 2:
580 if (self.config['force_multiplier']==1):
583 for i in range(len(plot.vectors[0][1])):
584 plot.vectors[0][1][i]=plot.vectors[0][1][i]*self.config['force_multiplier']
586 for i in range(len(plot.vectors[1][1])):
587 plot.vectors[1][1][i]=plot.vectors[1][1][i]*self.config['force_multiplier']
592 def plotmanip_flatten(self, plot, current, customvalue=False):
594 Subtracts a polynomial fit to the non-contact part of the curve, as to flatten it.
595 the best polynomial fit is chosen among polynomials of degree 1 to n, where n is
596 given by the configuration file or by the customvalue.
598 customvalue= int (>0) --> starts the function even if config says no (default=False)
602 if current.curve.experiment != 'smfs':
605 #only one set is present...
606 if len(self.plots[0].vectors) != 2:
609 #config is not flatten, and customvalue flag is false too
610 if (not self.config['flatten']) and (not customvalue):
617 max_cycles=customvalue
619 max_cycles=self.config['flatten'] #Using > 1 usually doesn't help and can give artefacts. However, it could be useful too.
621 contact_index=self.find_contact_point()
623 valn=[[] for item in range(max_exponent)]
624 yrn=[0.0 for item in range(max_exponent)]
625 errn=[0.0 for item in range(max_exponent)]
627 #Check if we have a proper numerical value
631 #Loudly and annoyingly complain if it's not a number, then fallback to zero
632 print '''Warning: flatten value is not a number!
633 Use "set flatten" or edit hooke.conf to set it properly
637 for i in range(int(max_cycles)):
639 x_ext=plot.vectors[0][0][contact_index+delta_contact:]
640 y_ext=plot.vectors[0][1][contact_index+delta_contact:]
641 x_ret=plot.vectors[1][0][contact_index+delta_contact:]
642 y_ret=plot.vectors[1][1][contact_index+delta_contact:]
643 for exponent in range(max_exponent):
645 valn[exponent]=sp.polyfit(x_ext,y_ext,exponent)
646 yrn[exponent]=sp.polyval(valn[exponent],x_ret)
647 errn[exponent]=sp.sqrt(sum((yrn[exponent]-y_ext)**2)/float(len(y_ext)))
649 print 'Cannot flatten!'
653 best_exponent=errn.index(min(errn))
656 ycorr_ext=y_ext-yrn[best_exponent]+y_ext[0] #noncontact part
657 yjoin_ext=np.array(plot.vectors[0][1][0:contact_index+delta_contact]) #contact part
659 ycorr_ret=y_ret-yrn[best_exponent]+y_ext[0] #noncontact part
660 yjoin_ret=np.array(plot.vectors[1][1][0:contact_index+delta_contact]) #contact part
662 ycorr_ext=np.concatenate((yjoin_ext, ycorr_ext))
663 ycorr_ret=np.concatenate((yjoin_ret, ycorr_ret))
665 plot.vectors[0][1]=list(ycorr_ext)
666 plot.vectors[1][1]=list(ycorr_ret)
671 def do_slope(self,args):
675 Measures the slope of a delimited chunk on the return trace.
676 The chunk can be delimited either by two manual clicks, or have
677 a fixed width, given as an argument.
679 Syntax: slope [width]
680 The facultative [width] parameter specifies how many
681 points will be considered for the fit. If [width] is
682 specified, only one click will be required.
683 (c) Marco Brucale, Massimo Sandal 2008
686 # Reads the facultative width argument
692 # Decides between the two forms of user input, as per (args)
694 # Gets the Xs of two clicked points as indexes on the current curve vector
695 print 'Click twice to delimit chunk'
696 points=self._measure_N_points(N=2,whatset=1)
698 print 'Click once on the leftmost point of the chunk (i.e.usually the peak)'
699 points=self._measure_N_points(N=1,whatset=1)
701 slope=self._slope(points,fitspan)
703 # Outputs the relevant slope parameter
706 to_dump='slope '+self.current.path+' '+str(slope)
707 self.outlet.push(to_dump)
709 def _slope(self,points,fitspan):
710 # Calls the function linefit_between
711 parameters=[0,0,[],[]]
713 clickedpoints=[points[0].index,points[1].index]
716 clickedpoints=[points[0].index-fitspan,points[0].index]
719 parameters=self.linefit_between(clickedpoints[0],clickedpoints[1])
721 print 'Cannot fit. Did you click twice the same point?'
724 # Outputs the relevant slope parameter
726 print str(parameters[0])
727 to_dump='slope '+self.curve.path+' '+str(parameters[0])
728 self.outlet.push(to_dump)
730 # Makes a vector with the fitted parameters and sends it to the GUI
731 xtoplot=parameters[2]
735 ytoplot.append((x*parameters[0])+parameters[1])
737 clickvector_x, clickvector_y=[], []
739 clickvector_x.append(item.graph_coords[0])
740 clickvector_y.append(item.graph_coords[1])
742 lineplot=self._get_displayed_plot(0) #get topmost displayed plot
744 lineplot.add_set(xtoplot,ytoplot)
745 lineplot.add_set(clickvector_x, clickvector_y)
748 if lineplot.styles==[]:
749 lineplot.styles=[None,None,None,'scatter']
751 lineplot.styles+=[None,'scatter']
752 if lineplot.colors==[]:
753 lineplot.colors=[None,None,'black',None]
755 lineplot.colors+=['black',None]
758 self._send_plot([lineplot])
763 def linefit_between(self,index1,index2,whatset=1):
765 Creates two vectors (xtofit,ytofit) slicing out from the
766 current return trace a portion delimited by the two indexes
768 Then does a least squares linear fit on that slice.
769 Finally returns [0]=the slope, [1]=the intercept of the
770 fitted 1st grade polynomial, and [2,3]=the actual (x,y) vectors
772 (c) Marco Brucale, Massimo Sandal 2008
774 # Translates the indexes into two vectors containing the x,y data to fit
775 xtofit=self.plots[0].vectors[whatset][0][index1:index2]
776 ytofit=self.plots[0].vectors[whatset][1][index1:index2]
778 # Does the actual linear fitting (simple least squares with numpy.polyfit)
780 linefit=np.polyfit(xtofit,ytofit,1)
782 return (linefit[0],linefit[1],xtofit,ytofit)
785 def fit_interval_nm(self,start_index,plot,nm,backwards):
787 Calculates the number of points to fit, given a fit interval in nm
788 start_index: index of point
790 backwards: if true, finds a point backwards.
792 whatset=1 #FIXME: should be decidable
793 x_vect=plot.vectors[1][0]
797 start=x_vect[start_index]
799 while abs(x_vect[i]-x_vect[start_index])*(10**9) < nm:
800 if i==0 or i==maxlen-1: #we reached boundaries of vector!
812 def find_current_peaks(self,noflatten, a=True, maxpeak=True):
815 a=self.convfilt_config['mindeviation']
819 print "Bad input, using default."
820 abs_devs=self.convfilt_config['mindeviation']
822 defplot=self.current.curve.default_plots()[0]
824 flatten=self._find_plotmanip('flatten') #Extract flatten plotmanip
825 defplot=flatten(defplot, self.current, customvalue=1) #Flatten curve before feeding it to has_peaks
826 pk_location,peak_size=self.has_peaks(defplot, abs_devs, maxpeak)
827 return pk_location, peak_size
830 def pickup_contact_point(self,N=1,whatset=1):
831 '''macro to pick up the contact point by clicking'''
832 contact_point=self._measure_N_points(N=1, whatset=1)[0]
833 contact_point_index=contact_point.index
834 self.wlccontact_point=contact_point
835 self.wlccontact_index=contact_point.index
836 self.wlccurrent=self.current.path
837 return contact_point, contact_point_index
840 def baseline_points(self,peak_location, displayed_plot):
841 clicks=self.config['baseline_clicks']
844 base_index_0=peak_location[-1]+self.fit_interval_nm(peak_location[-1], displayed_plot, self.config['auto_right_baseline'],False)
845 self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_0))
846 base_index_1=self.basepoints[0].index+self.fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'],False)
847 self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1))
849 print 'Select baseline'
851 self.basepoints=self._measure_N_points(N=1, whatset=1)
852 base_index_1=self.basepoints[0].index+self.fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'], False)
853 self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1))
855 self.basepoints=self._measure_N_points(N=2, whatset=1)
857 self.basecurrent=self.current.path
858 return self.basepoints