from ..command import Command, Argument, Failure, NullQueue
from ..config import Setting
from ..curve import Data
-from ..plugin import Builtin
+from ..plugin import Plugin
from ..util.fit import PoorFit, ModelFitter
+from ..util.si import join_data_label, split_data_label
from .curve import CurveArgument
-def scale(hooke, curve):
+def scale(hooke, curve, block=None):
+ """Run 'add block force array' on `block`.
+
+ Runs 'zero block surface contact point' first, if necessary. Does
+ not run either command if the columns they add to the block are
+ already present.
+
+ If `block` is `None`, scale all blocks in `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]
+ cant_adjust = [c for c in hooke.commands
+ if c.name == 'add block cantilever adjusted extension array'][0]
inqueue = None
outqueue = NullQueue()
- for i,block in enumerate(curve.data):
- numpy.savetxt(open('curve.dat', 'w'), block, delimiter='\t')
- params = {'curve':curve, 'block':i}
- try:
- contact._run(hooke, inqueue, outqueue, params)
- except PoorFit, e:
- raise PoorFit('Could not fit %s %s: %s'
- % (curve.path, i, str(e)))
- force._run(hooke, inqueue, outqueue, params)
+ if block == None:
+ for i in range(len(curve.data)):
+ scale(hooke, curve, block=i)
+ else:
+ params = {'curve':curve, 'block':block}
+ b = curve.data[block]
+ if ('surface distance (m)' not in b.info['columns']
+ or 'surface deflection (m)' not in b.info['columns']):
+ try:
+ contact.run(hooke, inqueue, outqueue, **params)
+ except PoorFit, e:
+ raise PoorFit('Could not fit %s %s: %s'
+ % (curve.path, block, str(e)))
+ if ('deflection (N)' not in b.info['columns']):
+ force.run(hooke, inqueue, outqueue, **params)
+ if ('cantilever adjusted extension (m)' not in b.info['columns']):
+ cant_adjust.run(hooke, inqueue, outqueue, **params)
return curve
class SurfacePositionModel (ModelFitter):
- """
+ """Bilinear surface position model.
The bilinear model is symmetric, but the parameter guessing and
sanity checks assume the contact region occurs for lower indicies
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
-----
-
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
% (params[3], self.info['guessed contact slope']))
return params
-class VelocityClampPlugin (Builtin):
+class VelocityClampPlugin (Plugin):
def __init__(self):
super(VelocityClampPlugin, self).__init__(name='vclamp')
self._commands = [
SurfaceContactCommand(self), ForceCommand(self),
+ CantileverAdjustedExtensionCommand(self),
]
def default_settings(self):
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.
-
You can select the contact point algorithm with the creatively
named `surface contact point algorithm` configuration setting.
Currently available options are:
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()),
+ Argument(name='input distance column', type='string',
+ default='z piezo (m)',
+ help="""
+Name of the column to use as the surface positioning input.
+""".strip()),
+ Argument(name='input deflection column', type='string',
+ default='deflection (m)',
+ help="""
+Name of the column to use as the deflection input.
+""".strip()),
+ Argument(name='output distance column', type='string',
+ default='surface distance',
+ help="""
+Name of the column (without units) to use as the surface positioning output.
+""".strip()),
+ Argument(name='output deflection column', type='string',
+ default='surface deflection',
+ help="""
+Name of the column (without units) to use as the deflection output.
+""".strip()),
+ Argument(name='distance info name', type='string',
+ default='surface distance offset',
+ help="""
+Name (without units) for storing the distance offset in the `.info` dictionary.
+""".strip()),
+ Argument(name='deflection info name', type='string',
+ default='surface deflection offset',
+ help="""
+Name (without units) for storing the deflection offset in the `.info` dictionary.
+""".strip()),
+ Argument(name='fit parameters info name', type='string',
+ default='surface deflection offset',
+ help="""
+Name (without units) for storing the deflection offset in the `.info` dictionary.
""".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
+ data = params['curve'].data[params['block']]
# 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
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(
- params['curve'], 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
+ name,dist_units = split_data_label(params['input distance column'])
+ name,def_units = split_data_label(params['input deflection column'])
+ new.info['columns'].extend([
+ join_data_label(params['output distance column'], dist_units),
+ join_data_label(params['output deflection column'], def_units),
+ ])
+ dist_data = data[:,data.info['columns'].index(
+ params['input distance column'])]
+ def_data = data[:,data.info['columns'].index(
+ params['input deflection column'])]
+ i,def_offset,ps = self.find_contact_point(
+ params['curve'], dist_data, def_data, outqueue)
+ dist_offset = dist_data[i]
+ new.info[join_data_label(params['distance info name'], dist_units
+ )] = dist_offset
+ new.info[join_data_label(params['deflection info name'], def_units
+ )] = def_offset
+ new.info[params['fit parameters info name']] = ps
+ new[:,-2] = dist_data - dist_offset
+ new[:,-1] = def_data - def_offset
+ params['curve'].data[params['block']] = new
def find_contact_point(self, curve, z_data, d_data, outqueue=None):
"""Railyard for the `find_contact_point_*` family.
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)`.
+class ForceCommand (Command):
+ """Convert a deflection column from meters to newtons.
"""
def __init__(self, plugin):
super(ForceCommand, self).__init__(
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()),
+ Argument(name='input deflection column', type='string',
+ default='surface deflection (m)',
+ help="""
+Name of the column to use as the deflection input.
+""".strip()),
+ Argument(name='output deflection column', type='string',
+ default='deflection',
+ help="""
+Name of the column (without units) to use as the deflection output.
+""".strip()),
+ Argument(name='spring constant info name', type='string',
+ default='spring constant (N/m)',
+ help="""
+Name of the spring constant in the `.info` dictionary.
""".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
+ data = params['curve'].data[params['block']]
# 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
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
+ new.info['columns'].append(
+ join_data_label(params['output deflection column'], 'N'))
+ d_data = data[:,data.info['columns'].index(
+ params['input deflection column'])]
+ new[:,-1] = d_data * data.info[params['spring constant info name']]
+ params['curve'].data[params['block']] = new
-class generalvclampCommands(object):
+class CantileverAdjustedExtensionCommand (Command):
+ """Calculate a block's `cantilever adjusted extension (m)` array.
- def do_subtplot(self, args):
- '''
- SUBTPLOT
- (procplots.py plugin)
- Plots the difference between ret and ext current curve
- -------
- Syntax: subtplot
- '''
- #FIXME: sub_filter and sub_order must be args
+ Uses the block's `deflection (m)` and `surface distance offset (m)`
+ arrays and `spring constant (N/m)`.
+ """
+ def __init__(self, plugin):
+ super(CantileverAdjustedExtensionCommand, self).__init__(
+ name='add block cantilever adjusted extension array',
+ arguments=[
+ CurveArgument,
+ Argument(name='block', aliases=['set'], type='int', default=0,
+ help="""
+Data block for which the adjusted extension 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)
- if len(self.plots[0].vectors) != 2:
- print 'This command only works on a curve with two different plots.'
- pass
+ def _run(self, hooke, inqueue, outqueue, params):
+ data = params['curve'].data[params['block']]
+ # 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('cantilever adjusted extension (m)')
+ z_data = data[:,data.info['columns'].index('surface distance (m)')]
+ d_data = data[:,data.info['columns'].index('deflection (N)')]
+ new[:,-1] = z_data - d_data / data.info['spring constant (N/m)']
+ params['curve'].data[params['block']] = new
- outplot=self.subtract_curves(sub_order=1)
- plot_graph=self.list_of_events['plot_graph']
- wx.PostEvent(self.frame,plot_graph(plots=[outplot]))
+class generalvclampCommands(object):
def _plug_init(self):
self.basecurrent=None