1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2008-2010 Alberto Gomez-Casado
5 # Massimo Sandal <devicerandom@gmail.com>
6 # W. Trevor King <wking@drexel.edu>
8 # This file is part of Hooke.
10 # Hooke is free software: you can redistribute it and/or
11 # modify it under the terms of the GNU Lesser General Public
12 # License as published by the Free Software Foundation, either
13 # version 3 of the License, or (at your option) any later version.
15 # Hooke is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU Lesser General Public License for more details.
20 # You should have received a copy of the GNU Lesser General Public
21 # License along with Hooke. If not, see
22 # <http://www.gnu.org/licenses/>.
24 """The ``convfilt`` module provides :class:`ConvFiltPlugin` and
25 for convolution-based filtering of :mod:`hooke.curve.Curve`\s.
29 :mod:`~hooke.plugin.flatfilt` for a collection of additional filters.
31 :class:`ConfFiltPlugin` was broken out of
32 :class:`~hooke.plugin.flatfilt` to separate its large number of
33 configuration settings.
37 from multiprocessing import Queue
41 from ..command import Command, Argument, Success, Failure
42 from ..config import Setting
43 from ..experiment import VelocityClamp
44 from ..plugin import Plugin, argument_to_setting
45 from ..plugin.curve import CurveArgument
46 from ..plugin.playlist import FilterCommand
47 from ..plugin.vclamp import scale
48 from ..util.peak import find_peaks, find_peaks_arguments, Peak, _kwargs
51 class ConvFiltPlugin (Plugin):
52 """Convolution-based peak recognition and filtering.
55 super(ConvFiltPlugin, self).__init__(name='convfilt')
56 self._arguments = [ # for Command initialization
57 Argument('convolution', type='float', count='-1',
58 default=[11.0]+[-1.0]*11, help="""
59 Convolution vector roughly matching post-peak cantilever rebound.
60 This should roughly match the shape of the feature you're looking for.
61 """.strip()), # TODO: per-curve convolution vector + interpolation, to
62 # take advantage of the known spring constant.
63 Argument('blind window', type='float', default=20e-9, help="""
64 Meters after the contact point where we do not count peaks to avoid
65 non-specific surface interaction.
67 Argument('min peaks', type='int', default=5, help="""
68 Minimum number of peaks for curve acceptance.
70 ] + copy.deepcopy(find_peaks_arguments)
71 # Set convolution-specific defaults for the fit_peak_arguments.
72 for key,value in [('cut side', 'positive'),
75 ('min deviations', 5.0),
77 ('see double', 10e-9),
79 argument = [a for a in self._arguments if a.name == key][0]
80 argument.default = value
82 Setting(section=self.setting_section, help=self.__doc__)]
83 for argument in self._arguments:
84 self._settings.append(argument_to_setting(
85 self.setting_section, argument))
86 argument.default = None # if argument isn't given, use the config.
87 self._commands = [ConvolutionPeaksCommand(self),
88 ConvolutionFilterCommand(self)]
90 def dependencies(self):
93 def default_settings(self):
97 class ConvolutionPeaksCommand (Command):
98 """Detect peaks in velocity clamp data with a convolution.
102 A simplified version of Kasas' filter [#kasas2000].
103 For any given retracting curve:
105 1) The contact point is found.
106 2) The off-surface data is convolved (using :func:`numpy.convolve`)
107 with a vector that encodes the approximate shape of the target
109 3) Peaks in the convolved curve are extracted with
110 :func:`~hooke.plugins.peak.find_peaks`.
112 The convolution algorithm, with appropriate thresholds, usually
113 recognizes peaks well more than 95% of the time.
115 .. [#kasas2000] S. Kasas, B.M. Riederer, S. Catsicas, B. Cappella,
117 "Fuzzy logic algorithm to extract specific interaction forces
118 from atomic force microscopy data"
119 Rev. Sci. Instrum., 2000.
120 doi: `10.1063/1.1150583 <http://dx.doi.org/10.1063/1.1150583>`_
122 def __init__(self, plugin):
123 config_arguments = [a for a in plugin._arguments
124 if a.name != 'min peaks']
125 # Drop min peaks, since we're not filtering with this
126 # function, just detecting peaks.
127 super(ConvolutionPeaksCommand, self).__init__(
128 name='convolution peaks',
129 arguments=[CurveArgument] + config_arguments,
130 help=self.__doc__, plugin=plugin)
132 def _run(self, hooke, inqueue, outqueue, params):
133 z_data,d_data,params = self._setup(params)
135 while z_data[start_index] < params['bind window']:
137 conv = numpy.convolve(d_data[start_index:], params['convolution'],
139 peaks = find_peaks(conv, **_kwargs(params, find_peaks_arguments,
140 argument_input_keys=True))
142 peak.name = 'convolution of %s with %s' \
143 % (params['deflection column name'], params['convolution'])
144 peak.index += start_index
147 def _setup(self, params):
148 """Setup `params` from config and return the z piezo and
151 curve = params['curve']
152 if curve.info['experiment'] != VelocityClamp:
153 raise Failure('%s operates on VelocityClamp experiments, not %s'
154 % (self.name, curve.info['experiment']))
155 for col in ['surface z piezo (m)', 'deflection (N)']:
156 if col not in curve.data[0].info['columns']:
159 for block in curve.data:
160 if block.info['name'].startswith('retract'):
164 raise Failure('No retraction blocks in %s.' % curve)
165 z_data = data[:,data.info['columns'].index('surface z piezo (m)')]
166 if 'flattened deflection (N)' in data.info['columns']:
167 params['deflection column name'] = 'flattened deflection (N)'
169 params['deflection column name'] = 'deflection (N)'
170 d_data = data[:,data.info['columns'].index(
171 params['deflection column name'])]
172 for key,value in params.items():
173 if value == None: # Use configured default value.
174 params[key] = self.plugin.config[key]
175 return z_data,d_data,params
178 class ConvolutionFilterCommand (FilterCommand):
179 u"""Create a subset playlist of curves with enough convolution peaks.
183 This filter can reduce a dataset like the one in [#brucale2009] to
184 analyze by hand by about 80-90% (depending on the overall
185 cleanliness of the data set). Thousands of curves can be
186 automatically filtered this way in a few minutes on a standard PC,
187 but the algorithm could still be optimized.
189 .. [#brucale2009] M. Brucale, M. Sandal, S. Di Maio, A. Rampioni,
190 I. Tessari, L. Tosatto, M. Bisaglia, L. Bubacco, B. Samorì.
191 "Pathogenic mutations shift the equilibria of
192 α-Synuclein single molecules towards structured
195 doi: `10.1002/cbic.200800581 <http://dx.doi.org/10.1002/cbic.200800581>`_
199 ConvolutionCommand : Underlying convolution-based peak detection.
201 def __init__(self, plugin):
202 super(ConvolutionFilterCommand, self).__init__(
203 plugin, name='convolution filter playlist')
204 self.arguments.extend(plugin._arguments)
206 def filter(self, curve, hooke, inqueue, outqueue, params):
209 conv_command = [c for c in self.hooke.commands
210 if c.name=='convolution peaks'][0]
211 conv_command.run(hooke, inq, outq, **params)
213 if not (isinstance(peaks, list) and (len(peaks) == 0
214 or isinstance(peaks[0], Peak))):
215 raise Failure('Expected a list of Peaks, not %s' % peaks)
217 if not isinstance(ret, Success):
219 if params['min peaks'] == None: # Use configured default value.
220 params['min peaks'] = self.plugin.config['min peaks']
221 return len(peaks) >= params['min peaks']