From: W. Trevor King Date: Wed, 2 Jun 2010 04:36:41 +0000 (-0400) Subject: Moved contact point detection from plugin.fit -> plugin.vclamp. X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=65225fe0dd6dc6896f767a7598a47be3854aa2dc;p=hooke.git Moved contact point detection from plugin.fit -> plugin.vclamp. I haven't translated the two algorithms brought over from fit yet, but I have added my own bilinear fit approach: _find_contact_point_wtk Also: * vclamp.scale gets access to hooke for the command list and command._run(hooke, ...) * vclamp.scale functionality broken out into SurfaceContactCommand and ForceCommand. * 'surface z piezo (m)' -> 'surface distance (m)' * Actually set params['curve'] for flatfilt and convfilt FilterCommand subclasses. Previous implementation just banged away on the default curve, regardless of the curve option passed to the subclass' _run. --- diff --git a/hooke/plugin/convfilt.py b/hooke/plugin/convfilt.py index 7ca9861..4be6ea8 100644 --- a/hooke/plugin/convfilt.py +++ b/hooke/plugin/convfilt.py @@ -130,7 +130,7 @@ class ConvolutionPeaksCommand (Command): help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): - z_data,d_data,params = self._setup(params) + z_data,d_data,params = self._setup(hooke, params) start_index = 0 while z_data[start_index] < params['bind window']: start_index += 1 @@ -144,7 +144,7 @@ class ConvolutionPeaksCommand (Command): peak.index += start_index outqueue.put(peaks) - def _setup(self, params): + def _setup(self, hooke, params): """Setup `params` from config and return the z piezo and deflection arrays. """ @@ -152,9 +152,9 @@ class ConvolutionPeaksCommand (Command): if curve.info['experiment'] != VelocityClamp: raise Failure('%s operates on VelocityClamp experiments, not %s' % (self.name, curve.info['experiment'])) - for col in ['surface z piezo (m)', 'deflection (N)']: + for col in ['surface distance (m)', 'deflection (N)']: if col not in curve.data[0].info['columns']: - scale(curve) + scale(hooke, curve) data = None for block in curve.data: if block.info['name'].startswith('retract'): @@ -162,7 +162,7 @@ class ConvolutionPeaksCommand (Command): break if data == None: raise Failure('No retraction blocks in %s.' % curve) - z_data = data[:,data.info['columns'].index('surface z piezo (m)')] + z_data = data[:,data.info['columns'].index('surface distance (m)')] if 'flattened deflection (N)' in data.info['columns']: params['deflection column name'] = 'flattened deflection (N)' else: @@ -204,6 +204,7 @@ class ConvolutionFilterCommand (FilterCommand): self.arguments.extend(plugin._arguments) def filter(self, curve, hooke, inqueue, outqueue, params): + params['curve'] = curve inq = Queue() outq = Queue() conv_command = [c for c in self.hooke.commands diff --git a/hooke/plugin/fit.py b/hooke/plugin/fit.py index 1718c1a..7ea2726 100644 --- a/hooke/plugin/fit.py +++ b/hooke/plugin/fit.py @@ -778,171 +778,3 @@ class fitCommands(object): 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] - - xext=plot.vectors[0][0] - 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 - monlength=len(xret)-int(len(xret)/20) - finalength=len(xret) - while monster: - monchunk=scipy.array(ydiff[monlength:finalength]) - if abs(np.std(monchunk)) < 2e-10: - monster=False - 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] - regr=scipy.stats.linregress(xchunk,ychunk)[0:2] - #we stop if we found an almost-horizontal fit or if we're going too short... - #FIXME: 0.1 and 6 here are "magic numbers" (although reasonable) - if (abs(regr[1]) > 0.1) and ( endlength < len(xret)-int(len(xret)/6) ) : - endlength+=10 - else: - 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 - except IndexError: - #The algorithm didn't find anything below the baseline! It should NEVER happen - 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] - yext=self.plots[0].vectors[0][1] - xret2=self.plots[0].vectors[1][0] - yret=self.plots[0].vectors[1][1] - ''' - xext=raw_plot.vectors[0][0] - 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]]) - #contact_plot.add_set([first_point[0]],[first_point[1]]) - #contact_plot.add_set([last_point[0]],[last_point[1]]) - 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 - raw_plot=self.current.curve.default_plots()[0] - xret=raw_plot.vectors[0][0] - #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) - contact_plot.styles=[None,None,None,None] - #contact_plot.styles.append(None) - contact_plot.destination=1 - self._send_plot([contact_plot]) diff --git a/hooke/plugin/flatfilt.py b/hooke/plugin/flatfilt.py index 1a49972..1637ce3 100644 --- a/hooke/plugin/flatfilt.py +++ b/hooke/plugin/flatfilt.py @@ -121,7 +121,7 @@ class FlatPeaksCommand (Command): help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): - z_data,d_data,params = self._setup(params) + z_data,d_data,params = self._setup(hooke, params) start_index = 0 while (start_index < len(z_data) and z_data[start_index] < params['blind window']): @@ -135,7 +135,7 @@ class FlatPeaksCommand (Command): peak.index += start_index outqueue.put(peaks) - def _setup(self, params): + def _setup(self, hooke, params): """Setup `params` from config and return the z piezo and deflection arrays. """ @@ -143,17 +143,17 @@ class FlatPeaksCommand (Command): if curve.info['experiment'] != VelocityClamp: raise Failure('%s operates on VelocityClamp experiments, not %s' % (self.name, curve.info['experiment'])) - for col in ['surface z piezo (m)', 'deflection (N)']: + for col in ['surface distance (m)', 'deflection (N)']: if col not in curve.data[0].info['columns']: - scale(curve) + scale(hooke, curve) data = None - for block in curve.data: + for i,block in enumerate(curve.data): if block.info['name'].startswith('retract'): data = block break if data == None: raise Failure('No retraction blocks in %s.' % curve) - z_data = data[:,data.info['columns'].index('surface z piezo (m)')] + z_data = data[:,data.info['columns'].index('surface distance (m)')] if 'flattened deflection (N)' in data.info['columns']: params['deflection column name'] = 'flattened deflection (N)' else: @@ -197,6 +197,7 @@ class FlatFilterCommand (FilterCommand): self.arguments.extend(plugin._arguments) def filter(self, curve, hooke, inqueue, outqueue, params): + params['curve'] = curve inq = Queue() outq = Queue() filt_command = [c for c in hooke.commands diff --git a/hooke/plugin/vclamp.py b/hooke/plugin/vclamp.py index 54498e4..2ab0372 100644 --- a/hooke/plugin/vclamp.py +++ b/hooke/plugin/vclamp.py @@ -27,35 +27,426 @@ common velocity clamp analysis tasks. import copy -from ..command import Command, Argument, Failure +import numpy + +from ..command import Command, Argument, Failure, NullQueue +from ..config import Setting from ..curve import Data from ..plugin import Builtin +from ..util.fit import PoorFit, ModelFitter +from .curve import CurveArgument -# Utility functions - -def scale(curve): +def scale(hooke, curve): + commands = hooke.commands + contact = [c for c in hooke.commands + if c.name == 'zero block surface contact point'][0] + force = [c for c in hooke.commands if c.name == 'add block force array'][0] + inqueue = None + outqueue = NullQueue() for i,block in enumerate(curve.data): - data = Data((block.shape[0], block.shape[1]+2), dtype=block.dtype) - data.info = copy.deepcopy(block.info) - data[:,:-2] = block - data.info['columns'].extend(['surface z piezo (m)', 'deflection (N)']) - z_data = data[:,data.info['columns'].index('z piezo (m)')] - d_data = data[:,data.info['columns'].index('deflection (m)')] - surface_offset = min(z_data) # TODO - data.info['surface offset (m)'] = surface_offset - data[:,-2] = z_data - surface_offset - data[:,-1] = d_data * data.info['spring constant (N/m)'] - curve.data[i] = data + params = {'curve':curve, 'block':i} + contact._run(hooke, inqueue, outqueue, params) + force._run(hooke, inqueue, outqueue, params) return curve +class SurfacePositionModel (ModelFitter): + """ + + The bilinear model is symmetric, but the parameter guessing and + sanity checks assume the contact region occurs for lower indicies + ("left of") the non-contact region. We also assume that + tip-surface attractions produce positive deflections. + + Notes + ----- + Algorithm borrowed from WTK's `piezo package`_, specifically + from :func:`piezo.z_piezo_utils.analyzeSurfPosData`. + + .. _piezo package: + http://www.physics.drexel.edu/~wking/code/git/git.php?p=piezo.git + + Fits the data to the bilinear :method:`model`. + + In order for this model to produce a satisfactory fit, there + should be enough data in the off-surface region that interactions + due to proteins, etc. will not seriously skew the fit in the + off-surface region. + + + We guess + """ + def model(self, params): + """A continuous, bilinear model. + + Notes + ----- + .. math:: + + y = \begin{cases} + p_0 + p_1 x & \text{if $x <= p_2$}, \\ + p_0 + p_1 p_2 + p_3 (x-p_2) & \text{if $x >= p_2$}. + \end{cases} + + Where :math:`p_0` is a vertical offset, :math:`p_1` is the slope + of the first region, :math:`p_2` is the transition location, and + :math:`p_3` is the slope of the second region. + """ + p = params # convenient alias + if self.info.get('force zero non-contact slope', None) == True: + p = list(p) + p.append(0.) # restore the non-contact slope parameter + r2 = numpy.round(p[2]) + if r2 >= 1: + self._model_data[:r2] = p[0] + p[1] * numpy.arange(r2) + if r2 < len(self._data)-1: + self._model_data[r2:] = p[0] + p[1]*p[2] + p[3] * numpy.arange(len(self._data)-r2) + return self._model_data + + def set_data(self, data, info=None): + super(SurfacePositionModel, self).set_data(data, info) + if info == None: + info = {} + self.info = info + self.info['min position'] = 0 + self.info['max position'] = len(data) + self.info['max deflection'] = data.max() + self.info['min deflection'] = data.min() + self.info['position range'] = self.info['max position'] - self.info['min position'] + self.info['deflection range'] = self.info['max deflection'] - self.info['min deflection'] + + def guess_initial_params(self, outqueue=None): + """Guess the initial parameters. + + Notes + ----- + We guess initial parameters such that the offset (:math:`p_1`) + matches the minimum deflection, the kink (:math:`p_2`) occurs in + the middle of the data, the initial (contact) slope (:math:`p_0`) + produces the maximum deflection at the left-most point, and the + final (non-contact) slope (:math:`p_3`) is zero. + """ + left_offset = self.info['max deflection'] + left_slope = 2*(-self.info['deflection range'] + /self.info['position range']) + kink_position = (self.info['max position'] + +self.info['min position'])/2.0 + right_slope = 0 + self.info['guessed contact slope'] = left_slope + params = [left_offset, left_slope, kink_position, right_slope] + if self.info.get('force zero non-contact slope', None) == True: + params = params[:-1] + return params + + def guess_scale(self, params, outqueue=None): + """Guess the parameter scales. + + Notes + ----- + + We guess offset scale (:math:`p_0`) as one tenth of the total + deflection range, the kink scale (:math:`p_2`) as one tenth of + the total index range, the initial (contact) slope scale + (:math:`p_1`) as one tenth of the contact slope estimation, + and the final (non-contact) slope scale (:math:`p_3`) is as + one tenth of the initial slope scale. + """ + offset_scale = self.info['deflection range']/10. + left_slope_scale = abs(params[1])/10. + kink_scale = self.info['position range']/10. + right_slope_scale = left_slope_scale/10. + scale = [offset_scale, left_slope_scale, kink_scale, right_slope_scale] + if self.info.get('force zero non-contact slope', None) == True: + scale = scale[:-1] + return scale + + def fit(self, *args, **kwargs): + self.info['guessed contact slope'] = None + params = super(SurfacePositionModel, self).fit(*args, **kwargs) + if self.info.get('force zero non-contact slope', None) == True: + params = list(params) + params.append(0.) # restore the non-contact slope parameter + + # check that the fit is reasonable, see the :meth:`model` docstring + # for parameter descriptions. + if abs(params[3]*10) > abs(params[1]) : + raise PoorFit('Slope in non-contact region, or no slope in contact') + if params[2] < self.info['min position']+0.02*self.info['position range']: + raise poorFit( + 'No kink (kink %g less than %g, need more space to left)' + % (params[2], + self.info['min position']+0.02*self.info['position range'])) + if params[2] > self.info['max position']-0.02*self.info['position range']: + raise poorFit( + 'No kink (kink %g more than %g, need more space to right)' + % (params[2], + self.info['max position']-0.02*self.info['position range'])) + if (self.info['guessed contact slope'] != None + and params[3] < 0.5 * self.info['guessed contact slope']): + raise PoorFit('Too far') + return params class VelocityClampPlugin (Builtin): def __init__(self): super(VelocityClampPlugin, self).__init__(name='vclamp') - self._commands = [] -# NextCommand(self), PreviousCommand(self), JumpCommand(self), -# ] + self._commands = [ + SurfaceContactCommand(self), ForceCommand(self), + ] + + def default_settings(self): + return [ + Setting(section=self.setting_section, help=self.__doc__), + Setting(section=self.setting_section, + option='surface contact point algorithm', + value='wtk', + help='Select the surface contact point algorithm. See the documentation for descriptions of available algorithms.') + ] + + +class SurfaceContactCommand (Command): + """Automatically determine a block's surface contact point. + + Uses the block's `z piezo (m)` and `deflection (m)` arrays. + Stores the contact parameters in `block.info`'s `surface distance + offset (m)` and `surface deflection offset (m)`. Model-specific + fitting information is stored in `surface detection parameters`. + + The adjusted data columns `surface distance (m)` and `surface + adjusted deflection (m)` are also added to the block. + """ + def __init__(self, plugin): + super(SurfaceContactCommand, self).__init__( + name='zero block surface contact point', + arguments=[ + CurveArgument, + Argument(name='block', aliases=['set'], type='int', default=0, + help=""" +Data block for which the force should be calculated. For an +approach/retract force curve, `0` selects the approaching curve and `1` +selects the retracting curve. +""".strip()), + ], + help=self.__doc__, plugin=plugin) + + def _run(self, hooke, inqueue, outqueue, params): + data = params['curve'].data[int(params['block'])] # HACK, int() should be handled by ui + # HACK? rely on params['curve'] being bound to the local hooke + # playlist (i.e. not a copy, as you would get by passing a + # curve through the queue). Ugh. Stupid queues. As an + # alternative, we could pass lookup information through the + # queue... + new = Data((data.shape[0], data.shape[1]+2), dtype=data.dtype) + new.info = copy.deepcopy(data.info) + new[:,:-2] = data + new.info['columns'].extend( + ['surface distance (m)', 'surface adjusted deflection (m)']) + z_data = data[:,data.info['columns'].index('z piezo (m)')] + d_data = data[:,data.info['columns'].index('deflection (m)')] + i,deflection_offset,ps = self._find_contact_point( + z_data, d_data, outqueue) + surface_offset = z_data[i] + new.info['surface distance offset (m)'] = surface_offset + new.info['surface deflection offset (m)'] = deflection_offset + new.info['surface detection parameters'] = ps + new[:,-2] = z_data - surface_offset + new[:,-1] = d_data - deflection_offset + data = params['curve'].data[int(params['block'])] # HACK, int() should be handled by ui + params['curve'].data[int(params['block'])] = new # HACK, int() should be handled by ui + + def _find_contact_point(self, z_data, d_data, outqueue=None): + fn = getattr(self, '_find_contact_point_%s' + % self.plugin.config['surface contact point algorithm']) + return fn(z_data, d_data, outqueue) + + def _find_contact_point_fmms(self, z_data, d_data, outqueue=None): + """Algorithm by Francesco Musiani and Massimo Sandal. + + Notes + ----- + Algorithm: + + * 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. + """ + if not plot: + plot=self.plots[0] + + outplot=self.subtract_curves(1) + xret=outplot.vectors[1][0] + ydiff=outplot.vectors[1][1] + + xext=plot.vectors[0][0] + 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 + monlength=len(xret)-int(len(xret)/20) + finalength=len(xret) + while monster: + monchunk=scipy.array(ydiff[monlength:finalength]) + if abs(np.std(monchunk)) < 2e-10: + monster=False + 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] + regr=scipy.stats.linregress(xchunk,ychunk)[0:2] + #we stop if we found an almost-horizontal fit or if we're going too short... + #FIXME: 0.1 and 6 here are "magic numbers" (although reasonable) + if (abs(regr[1]) > 0.1) and ( endlength < len(xret)-int(len(xret)/6) ) : + endlength+=10 + else: + 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 + except IndexError: + #The algorithm didn't find anything below the baseline! It should NEVER happen + index=0 + return index + + return index + + def _find_contact_point_ms(self, z_data, d_data, outqueue=None): + """Algorithm by Massimo Sandal. + + Notes + ----- + WTK: At least the commits are by Massimo, and I see no notes + attributing the algorithm to anyone else. + + Algorithm: + + * ? + """ + #raw_plot=self.current.curve.default_plots()[0] + raw_plot=self.plots[0] + '''xext=self.plots[0].vectors[0][0] + yext=self.plots[0].vectors[0][1] + xret2=self.plots[0].vectors[1][0] + yret=self.plots[0].vectors[1][1] + ''' + xext=raw_plot.vectors[0][0] + 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 _find_contact_point_wtk(self, z_data, d_data, outqueue=None): + """Algorithm by W. Trevor King. + + Notes + ----- + Uses :func:`analyze_surf_pos_data_wtk` internally. + """ + reverse = z_data[0] > z_data[-1] + if reverse == True: # approaching, contact region on the right + d_data = d_data[::-1] + s = SurfacePositionModel(d_data) + s.info['force zero non-contact slope'] = True + offset,contact_slope,surface_index,non_contact_slope = s.fit( + outqueue=outqueue) + info = { + 'offset': offset, + 'contact slope': contact_slope, + 'surface index': surface_index, + 'non-contact slope': non_contact_slope, + 'reversed': reverse, + } + deflection_offset = offset + contact_slope*surface_index, + if reverse == True: + surface_index = len(d_data)-1-surface_index + return (numpy.round(surface_index), deflection_offset, info) + +class ForceCommand (Command): + """Calculate a block's `deflection (N)` array. + + Uses the block's `deflection (m)` array and `spring constant + (N/m)`. + """ + def __init__(self, plugin): + super(ForceCommand, self).__init__( + name='add block force array', + arguments=[ + CurveArgument, + Argument(name='block', aliases=['set'], type='int', default=0, + help=""" +Data block for which the force should be calculated. For an +approach/retract force curve, `0` selects the approaching curve and `1` +selects the retracting curve. +""".strip()), + ], + help=self.__doc__, plugin=plugin) + + def _run(self, hooke, inqueue, outqueue, params): + data = params['curve'].data[int(params['block'])] # HACK, int() should be handled by ui + # HACK? rely on params['curve'] being bound to the local hooke + # playlist (i.e. not a copy, as you would get by passing a + # curve through the queue). Ugh. Stupid queues. As an + # alternative, we could pass lookup information through the + # queue. + new = Data((data.shape[0], data.shape[1]+1), dtype=data.dtype) + new.info = copy.deepcopy(data.info) + new[:,:-1] = data + new.info['columns'].append('deflection (N)') + d_data = data[:,data.info['columns'].index('surface adjusted deflection (m)')] + new[:,-1] = d_data * data.info['spring constant (N/m)'] + params['curve'].data[int(params['block'])] = new # HACK, int() should be handled by ui + class generalvclampCommands(object): @@ -452,7 +843,6 @@ class generalvclampCommands(object): return contact_point, contact_point_index - def baseline_points(self,peak_location, displayed_plot): clicks=self.config['baseline_clicks'] if clicks==0: