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 ``flatfilt`` module provides :class:`FlatFiltPlugin` and
22 several associated :class:`~hooke.command.Command`\s for removing flat
23 (featureless) :mod:`~hooke.curve.Curve`\s from
24 :class:`~hooke.playlist.Playlist`\s.
28 :mod:`~hooke.plugin.convfilt` for a convolution-based filter.
32 from Queue import Queue
34 from numpy import absolute, diff
35 from scipy.signal.signaltools import medfilt
37 from ..command import Argument, Success, Failure, UncaughtException
38 from ..config import Setting
39 from ..curve import Data
40 from ..util.fit import PoorFit
41 from ..util.peak import (find_peaks, peaks_to_mask,
42 find_peaks_arguments, Peak, _kwargs)
43 from ..util.si import join_data_label, split_data_label
44 from . import Plugin, argument_to_setting
45 from .curve import ColumnAddingCommand
46 from .playlist import FilterCommand
49 class FlatFiltPlugin (Plugin):
50 """Standard-devitiation-based peak recognition and filtering.
53 super(FlatFiltPlugin, self).__init__(name='flatfilt')
54 self._arguments = [ # For Command initialization
55 Argument('median window', type='int', default=7, help="""
56 Median window filter size (in points).
58 Argument('blind window', type='float', default=20e-9, help="""
59 Meters after the contact point where we do not count peaks to avoid
60 non-specific surface interaction.
62 Argument('min peaks', type='int', default=4, help="""
63 Minimum number of peaks for curve acceptance.
65 ] + copy.deepcopy(find_peaks_arguments)
66 # Set flat-filter-specific defaults for the fit_peak_arguments.
67 for key,value in [('cut side', 'both'),
70 ('min deviations', 9.0),
72 ('see double', 10), # TODO: points vs. meters. 10e-9),
74 argument = [a for a in self._arguments if a.name == key][0]
75 argument.default = value
77 Setting(section=self.setting_section, help=self.__doc__)]
78 for argument in self._arguments:
79 self._settings.append(argument_to_setting(
80 self.setting_section, argument))
81 argument.default = None # if argument isn't given, use the config.
82 self._commands = [FlatPeaksCommand(self)]
83 # append FlatFilterCommand so it can steal arguments from
85 self._commands.append(FlatFilterCommand(self))
87 def dependencies(self):
90 def default_settings(self):
94 class FlatPeaksCommand (ColumnAddingCommand):
95 """Detect peaks in velocity clamp data using noise statistics.
99 Noise analysis on the retraction curve:
101 1) A median window filter (using
102 :func:`scipy.signal.signaltools.medfilt`) smooths the
104 2) The deflection derivative is calculated (using
105 :func:`numpy.diff` which uses forward differencing).
106 3) Peaks in the derivative curve are extracted with
107 :func:`~hooke.plugins.peak.find_peaks`.
109 The algorithm was originally Francesco Musiani's idea.
111 def __init__(self, plugin):
112 plugin_arguments = [a for a in plugin._arguments
113 if a.name != 'min peaks']
114 # Drop min peaks, since we're not filtering with this
115 # function, just detecting peaks.
116 super(FlatPeaksCommand, self).__init__(
117 name='flat filter peaks',
119 ('distance column', 'surface distance (m)', """
120 Name of the column to use as the surface position input.
122 ('deflection column', 'surface deflection (m)', """
123 Name of the column to use as the deflection input.
127 ('output peak column', 'flat filter peaks', """
128 Name of the column (without units) to use as the peak output.
132 Argument(name='peak info name', type='string',
133 default='flat filter peaks',
135 Name for storing the list of peaks in the `.info` dictionary.
137 ] + plugin_arguments,
138 help=self.__doc__, plugin=plugin)
140 def _run(self, hooke, inqueue, outqueue, params):
141 self._add_to_command_stack(params)
142 params = self._setup_params(hooke=hooke, params=params)
143 block = self._block(hooke=hooke, params=params)
144 dist_data = self._get_column(hooke=hooke, params=params,
145 column_name='distance column')
146 def_data = self._get_column(hooke=hooke, params=params,
147 column_name='deflection column')
148 start_index = absolute(dist_data-params['blind window']).argmin()
149 median = medfilt(def_data[start_index:], params['median window'])
151 peaks = find_peaks(deriv, **_kwargs(params, find_peaks_arguments,
152 argument_input_keys=True))
153 for i,peak in enumerate(peaks):
154 peak.name = self._peak_name(params, i)
155 peak.index += start_index
156 block.info[params['peak info name']] = peaks
158 self._set_column(hooke=hooke, params=params,
159 column_name='output peak column',
160 values=peaks_to_mask(def_data, peaks) * def_data)
163 def _setup_params(self, hooke, params):
164 """Setup `params` from config and return the z piezo and
167 curve = self._curve(hooke=hooke, params=params)
168 for key,value in params.items():
169 if value == None and key in self.plugin.config:
170 # Use configured default value.
171 params[key] = self.plugin.config[key]
172 # TODO: convert 'see double' from nm to points
173 name,def_unit = split_data_label(params['deflection column'])
174 params['output peak column'] = join_data_label(
175 params['output peak column'], def_unit)
178 def _peak_name(self, params, index):
179 d_name,d_unit = split_data_label(params['deflection column'])
180 return 'flat filter peak %d of %s' % (index, d_name)
183 class FlatFilterCommand (FilterCommand):
184 u"""Create a subset playlist of curves with enough flat peaks.
188 This type of filter is of course very raw, and requires relatively
189 conservative settings to safely avoid false negatives (that is, to
190 avoid discarding interesting curves). Using it on the protein
191 unfolding experiments described by Sandal [#sandal2008] it has
192 been found to reduce the data set to analyze by hand by 60-80%.
194 .. [#sandal2008] M. Sandal, F. Valle, I. Tessari, S. Mammi, E. Bergantino,
195 F. Musiani, M. Brucale, L. Bubacco, B. Samorì.
196 "Conformational equilibria in monomeric α-Synuclein at the
197 single molecule level."
199 doi: `10.1371/journal.pbio.0060006 <http://dx.doi.org/10.1371/journal.pbio.0060006>`_
203 FlatPeaksCommand : Underlying flat-based peak detection.
205 def __init__(self, plugin):
206 flat_peaks = [c for c in plugin._commands
207 if c.name == 'flat filter peaks'][0]
208 flat_peaks_arguments = [a for a in flat_peaks.arguments
209 if a.name not in ['help', 'stack']]
210 flat_peaks_arg_names = [a.name for a in flat_peaks_arguments]
211 plugin_arguments = [a for a in plugin._arguments
212 if a.name not in flat_peaks_arg_names]
213 arguments = flat_peaks_arguments + plugin_arguments
214 super(FlatFilterCommand, self).__init__(
215 plugin, name='flat filter playlist')
216 self.arguments.extend(arguments)
218 def filter(self, curve, hooke, inqueue, outqueue, params):
219 params['curve'] = curve
222 filt_command = hooke.command_by_name['flat filter peaks']
223 filt_command.run(hooke, inq, outq, **params)
225 if isinstance(peaks, UncaughtException) \
226 and isinstance(peaks.exception, PoorFit):
228 if not (isinstance(peaks, list) and (len(peaks) == 0
229 or isinstance(peaks[0], Peak))):
230 raise Failure('Expected a list of Peaks, not %s: %s'
231 % (type(peaks), peaks))
233 if not isinstance(ret, Success):
235 if params['min peaks'] == None: # Use configured default value.
236 params['min peaks'] = self.plugin.config['min peaks']
237 return len(peaks) >= int(params['min peaks'])