1 # Copyright (C) 2008-2010 Alberto Gomez-Casado
3 # Massimo Sandal <devicerandom@gmail.com>
4 # W. Trevor King <wking@drexel.edu>
6 # This file is part of Hooke.
8 # Hooke is free software: you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation, either
11 # version 3 of the License, or (at your option) any later version.
13 # Hooke is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Lesser General Public License for more details.
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with Hooke. If not, see
20 # <http://www.gnu.org/licenses/>.
22 """The ``curve`` module provides :class:`CurvePlugin` and several
23 associated :class:`hooke.command.Command`\s for handling
24 :mod:`hooke.curve` classes.
29 from ..command import Command, Argument, Failure
30 from ..curve import Data
31 from ..plugin import Builtin
32 from ..plugin.playlist import current_playlist_callback
33 from ..util.calculus import derivative
34 from ..util.fft import unitary_avg_power_spectrum
37 class CurvePlugin (Builtin):
39 super(CurvePlugin, self).__init__(name='curve')
41 GetCommand(self), InfoCommand(self), ExportCommand(self),
42 DifferenceCommand(self), DerivativeCommand(self),
43 PowerSpectrumCommand(self)]
46 # Define common or complicated arguments
48 def current_curve_callback(hooke, command, argument, value):
51 playlist = current_playlist_callback(hooke, command, argument, value)
52 curve = playlist.current()
54 raise Failure('No curves in %s' % playlist)
57 CurveArgument = Argument(
58 name='curve', type='curve', callback=current_curve_callback,
60 :class:`hooke.curve.Curve` to act on. Defaults to the current curve
61 of the current playlist.
67 class GetCommand (Command):
68 """Return a :class:`hooke.curve.Curve`.
70 def __init__(self, plugin):
71 super(GetCommand, self).__init__(
72 name='get curve', arguments=[CurveArgument],
73 help=self.__doc__, plugin=plugin)
75 def _run(self, hooke, inqueue, outqueue, params):
76 outqueue.put(params['curve'])
78 class InfoCommand (Command):
79 """Get selected information about a :class:`hooke.curve.Curve`.
81 def __init__(self, plugin):
84 Argument(name='all', type='bool', default=False, count=1,
85 help='Get all curve information.'),
87 self.fields = ['name', 'path', 'experiment', 'driver', 'filetype', 'note',
88 'blocks', 'block sizes']
89 for field in self.fields:
91 name=field, type='bool', default=False, count=1,
92 help='Get curve %s' % field))
93 super(InfoCommand, self).__init__(
94 name='curve info', arguments=args,
95 help=self.__doc__, plugin=plugin)
97 def _run(self, hooke, inqueue, outqueue, params):
99 for key in self.fields:
100 fields[key] = params[key]
101 if reduce(lambda x,y: x and y, fields.values()) == False:
102 params['all'] = True # No specific fields set, default to 'all'
103 if params['all'] == True:
104 for key in self.fields:
107 for key in self.fields:
108 if fields[key] == True:
109 get = getattr(self, '_get_%s' % key.replace(' ', '_'))
110 lines.append('%s: %s' % (key, get(params['curve'])))
111 outqueue.put('\n'.join(lines))
113 def _get_name(self, curve):
116 def _get_path(self, curve):
119 def _get_experiment(self, curve):
120 return curve.info.get('experiment', None)
122 def _get_driver(self, curve):
125 def _get_filetype(self, curve):
126 return curve.info.get('filetype', None)
128 def _get_note(self, curve):
129 return curve.info.get('note', None)
131 def _get_blocks(self, curve):
132 return len(curve.data)
134 def _get_block_sizes(self, curve):
135 return [block.shape for block in curve.data]
137 class ExportCommand (Command):
138 """Export a :class:`hooke.curve.Curve` data block as TAB-delimeted
141 A "#" prefixed header will optionally appear at the beginning of
142 the file naming the columns.
144 def __init__(self, plugin):
145 super(ExportCommand, self).__init__(
149 Argument(name='block', aliases=['set'], type='int', default=0,
151 Data block to save. For an approach/retract force curve, `0` selects
152 the approaching curve and `1` selects the retracting curve.
154 Argument(name='output', type='file', default='curve.dat',
156 File name for the output data. Defaults to 'curve.dat'
158 Argument(name='header', type='bool', default=True,
160 True if you want the column-naming header line.
163 help=self.__doc__, plugin=plugin)
165 def _run(self, hooke, inqueue, outqueue, params):
166 data = params['curve'].data[int(params['block'])] # HACK, int() should be handled by ui
168 f = open(params['output'], 'w')
169 if params['header'] == True:
170 f.write('# %s \n' % ('\t'.join(data.info['columns'])))
171 numpy.savetxt(f, data, delimiter='\t')
174 class DifferenceCommand (Command):
175 """Calculate the derivative (actually, the discrete differentiation)
176 of a curve data block.
178 See :func:`hooke.util.calculus.derivative` for implementation
181 def __init__(self, plugin):
182 super(DifferenceCommand, self).__init__(
183 name='block difference',
186 Argument(name='block one', aliases=['set one'], type='int',
189 Block A in A-B. For an approach/retract force curve, `0` selects the
190 approaching curve and `1` selects the retracting curve.
192 Argument(name='block two', aliases=['set two'], type='int',
194 help='Block B in A-B.'),
195 Argument(name='x column', type='int', default=0,
197 Column of data block to differentiate with respect to.
199 Argument(name='f column', type='int', default=1,
201 Column of data block to differentiate.
204 help=self.__doc__, plugin=plugin)
206 def _run(self, hooke, inqueue, outqueue, params):
207 a = params['curve'].data[params['block one']]
208 b = params['curve'].data[params['block two']]
209 assert a[:,params['x column']] == b[:,params['x column']]
210 out = Data((a.shape[0],2), dtype=a.dtype)
211 out[:,0] = a[:,params['x column']]
212 out[:,1] = a[:,params['f column']] - b[:,params['f column']]
215 class DerivativeCommand (Command):
216 """Calculate the difference between two blocks of data.
218 def __init__(self, plugin):
219 super(DerivativeCommand, self).__init__(
220 name='block derivative',
223 Argument(name='block', aliases=['set'], type='int', default=0,
225 Data block to differentiate. For an approach/retract force curve, `0`
226 selects the approaching curve and `1` selects the retracting curve.
228 Argument(name='x column', type='int', default=0,
230 Column of data block to differentiate with respect to.
232 Argument(name='f column', type='int', default=1,
234 Column of data block to differentiate.
236 Argument(name='weights', type='dict', default={-1:-0.5, 1:0.5},
238 Weighting scheme dictionary for finite differencing. Defaults to
239 central differencing.
242 help=self.__doc__, plugin=plugin)
244 def _run(self, hooke, inqueue, outqueue, params):
245 data = params['curve'].data[params['block']]
246 outqueue.put(derivative(
247 block, x_col=params['x column'], f_col=params['f column'],
248 weights=params['weights']))
250 class PowerSpectrumCommand (Command):
251 """Calculate the power spectrum of a data block.
253 def __init__(self, plugin):
254 super(PowerSpectrumCommand, self).__init__(
255 name='block power spectrum',
258 Argument(name='block', aliases=['set'], type='int', default=0,
260 Data block to act on. For an approach/retract force curve, `0`
261 selects the approaching curve and `1` selects the retracting curve.
263 Argument(name='f column', type='int', default=1,
265 Column of data block to differentiate with respect to.
267 Argument(name='freq', type='float', default=1.0,
271 Argument(name='chunk size', type='int', default=2048,
273 Number of samples per chunk. Use a power of two.
275 Argument(name='overlap', type='bool', default=False,
277 If `True`, each chunk overlaps the previous chunk by half its length.
278 Otherwise, the chunks are end-to-end, and not overlapping.
281 help=self.__doc__, plugin=plugin)
283 def _run(self, hooke, inqueue, outqueue, params):
284 data = params['curve'].data[params['block']]
285 outqueue.put(unitary_avg_power_spectrum(
286 data[:,params['f column']], freq=params['freq'],
287 chunk_size=params['chunk size'],
288 overlap=params['overlap']))