1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2010-2012 W. Trevor King <wking@drexel.edu>
5 # This file is part of Hooke.
7 # Hooke is free software: you can redistribute it and/or modify it
8 # under the terms of the GNU Lesser General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # Hooke is distributed in the hope that it will be useful, but WITHOUT
13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
15 # Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with Hooke. If not, see
19 # <http://www.gnu.org/licenses/>.
21 """The ``convfilt`` module provides :class:`ConvFiltPlugin` and
22 for convolution-based filtering of :mod:`hooke.curve.Curve`\s.
26 :mod:`~hooke.plugin.flatfilt` for a collection of additional filters.
28 :class:`ConfFiltPlugin` was broken out of
29 :class:`~hooke.plugin.flatfilt` to separate its large number of
30 configuration settings.
34 from multiprocessing import Queue
38 from ..command import Command, Argument, Success, Failure
39 from ..config import Setting
40 from ..util.fit import PoorFit
41 from ..util.peak import find_peaks, find_peaks_arguments, Peak, _kwargs
42 from . import Plugin, argument_to_setting
43 from .curve import CurveArgument
44 from .playlist import FilterCommand
47 class ConvFiltPlugin (Plugin):
48 """Convolution-based peak recognition and filtering.
51 super(ConvFiltPlugin, self).__init__(name='convfilt')
52 self._arguments = [ # for Command initialization
53 Argument('convolution', type='float', count=-1,
54 default=[11.0]+[-1.0]*11, help="""
55 Convolution vector roughly matching post-peak cantilever rebound.
56 This should roughly match the shape of the feature you're looking for.
57 """.strip()), # TODO: per-curve convolution vector + interpolation, to
58 # take advantage of the known spring constant.
59 Argument('blind window', type='float', default=20e-9, help="""
60 Meters after the contact point where we do not count peaks to avoid
61 non-specific surface interaction.
63 Argument('min peaks', type='int', default=5, help="""
64 Minimum number of peaks for curve acceptance.
66 ] + copy.deepcopy(find_peaks_arguments)
67 # Set convolution-specific defaults for the fit_peak_arguments.
68 for key,value in [('cut side', 'positive'),
71 ('min deviations', 5.0),
73 ('see double', 10), # TODO: points vs. meters. 10e-9),
75 argument = [a for a in self._arguments if a.name == key][0]
76 argument.default = value
78 Setting(section=self.setting_section, help=self.__doc__)]
79 for argument in self._arguments:
80 self._settings.append(argument_to_setting(
81 self.setting_section, argument))
82 argument.default = None # if argument isn't given, use the config.
83 self._commands = [ConvolutionPeaksCommand(self),
84 ConvolutionFilterCommand(self)]
86 def dependencies(self):
89 def default_settings(self):
93 class ConvolutionPeaksCommand (Command):
94 """Detect peaks in velocity clamp data with a convolution.
98 A simplified version of Kasas' filter [#kasas2000].
99 For any given retracting curve:
101 1) The contact point is found.
102 2) The off-surface data is convolved (using :func:`numpy.convolve`)
103 with a vector that encodes the approximate shape of the target
105 3) Peaks in the convolved curve are extracted with
106 :func:`~hooke.plugins.peak.find_peaks`.
108 The convolution algorithm, with appropriate thresholds, usually
109 recognizes peaks well more than 95% of the time.
111 .. [#kasas2000] S. Kasas, B.M. Riederer, S. Catsicas, B. Cappella,
113 "Fuzzy logic algorithm to extract specific interaction forces
114 from atomic force microscopy data"
115 Rev. Sci. Instrum., 2000.
116 doi: `10.1063/1.1150583 <http://dx.doi.org/10.1063/1.1150583>`_
118 def __init__(self, plugin):
119 config_arguments = [a for a in plugin._arguments
120 if a.name != 'min peaks']
121 # Drop min peaks, since we're not filtering with this
122 # function, just detecting peaks.
123 super(ConvolutionPeaksCommand, self).__init__(
124 name='convolution peaks',
125 arguments=[CurveArgument] + config_arguments,
126 help=self.__doc__, plugin=plugin)
128 def _run(self, hooke, inqueue, outqueue, params):
129 z_data,d_data,params = self._setup(hooke, params)
131 while z_data[start_index] < params['bind window']:
133 conv = numpy.convolve(d_data[start_index:], params['convolution'],
135 peaks = find_peaks(conv, **_kwargs(params, find_peaks_arguments,
136 argument_input_keys=True))
138 peak.name = 'convolution of %s with %s' \
139 % (params['deflection column name'], params['convolution'])
140 peak.index += start_index
143 def _setup(self, hooke, params):
144 """Setup `params` from config and return the z piezo and
147 curve = params['curve']
149 for block in curve.data:
150 if block.info['name'].startswith('retract'):
154 raise Failure('No retraction blocks in %s.' % curve)
155 z_data = data[:,data.info['columns'].index('surface distance (m)')]
156 if 'flattened deflection (N)' in data.info['columns']:
157 params['deflection column name'] = 'flattened deflection (N)'
159 params['deflection column name'] = 'deflection (N)'
160 d_data = data[:,data.info['columns'].index(
161 params['deflection column name'])]
162 for key,value in params.items():
163 if value == None: # Use configured default value.
164 params[key] = self.plugin.config[key]
165 return z_data,d_data,params
168 class ConvolutionFilterCommand (FilterCommand):
169 u"""Create a subset playlist of curves with enough convolution peaks.
173 This filter can reduce a dataset like the one in [#brucale2009] to
174 analyze by hand by about 80-90% (depending on the overall
175 cleanliness of the data set). Thousands of curves can be
176 automatically filtered this way in a few minutes on a standard PC,
177 but the algorithm could still be optimized.
179 .. [#brucale2009] M. Brucale, M. Sandal, S. Di Maio, A. Rampioni,
180 I. Tessari, L. Tosatto, M. Bisaglia, L. Bubacco, B. Samorì.
181 "Pathogenic mutations shift the equilibria of
182 α-Synuclein single molecules towards structured
185 doi: `10.1002/cbic.200800581 <http://dx.doi.org/10.1002/cbic.200800581>`_
189 ConvolutionCommand : Underlying convolution-based peak detection.
191 def __init__(self, plugin):
192 super(ConvolutionFilterCommand, self).__init__(
193 plugin, name='convolution filter playlist')
194 self.arguments.extend(plugin._arguments)
196 def filter(self, curve, hooke, inqueue, outqueue, params):
197 params['curve'] = curve
200 conv_command = self.hooke.command_by_name['convolution peaks']
201 conv_command.run(hooke, inq, outq, **params)
203 if isinstance(peaks, UncaughtException) \
204 and isinstance(peaks.exception, PoorFit):
206 if not (isinstance(peaks, list) and (len(peaks) == 0
207 or isinstance(peaks[0], Peak))):
208 raise Failure('Expected a list of Peaks, not %s' % peaks)
210 if not isinstance(ret, Success):
212 if params['min peaks'] == None: # Use configured default value.
213 params['min peaks'] = self.plugin.config['min peaks']
214 return len(peaks) >= params['min peaks']