1 # Copyright (C) 2010-2012 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
13 # Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with Hooke. If not, see
17 # <http://www.gnu.org/licenses/>.
19 """The ``curve`` module provides :class:`CurvePlugin` and several
20 associated :class:`hooke.command.Command`\s for handling
21 :mod:`hooke.curve` classes.
31 from ..command import Command, Argument, Failure
32 from ..command_stack import CommandStack
33 from ..curve import Data
34 from ..engine import CommandMessage
35 from ..util.calculus import derivative
36 from ..util.fft import unitary_avg_power_spectrum
37 from ..util.si import ppSI, join_data_label, split_data_label
39 from .playlist import current_playlist_callback
42 # Define common or complicated arguments
44 def current_curve_callback(hooke, command, argument, value, load=True):
47 playlist = current_playlist_callback(hooke, command, argument, value)
48 curve = playlist.current(load=load)
50 raise Failure('No curves in %s' % playlist)
53 def unloaded_current_curve_callback(hooke, command, argument, value):
54 return current_curve_callback(
55 hooke=hooke, command=command, argument=argument, value=value,
58 CurveArgument = Argument(
59 name='curve', type='curve', callback=current_curve_callback,
61 :class:`hooke.curve.Curve` to act on. Defaults to the current curve
62 of the current playlist.
65 def _name_argument(name, default, help):
68 return Argument(name=name, type='string', default=default, help=help)
70 def block_argument(*args, **kwargs):
73 return _name_argument(*args, **kwargs)
75 def column_argument(*args, **kwargs):
78 return _name_argument(*args, **kwargs)
81 # Define useful command subclasses
83 class CurveCommand (Command):
84 """A :class:`~hooke.command.Command` operating on a
85 :class:`~hooke.curve.Curve`.
87 def __init__(self, **kwargs):
88 if 'arguments' in kwargs:
89 kwargs['arguments'].insert(0, CurveArgument)
91 kwargs['arguments'] = [CurveArgument]
92 super(CurveCommand, self).__init__(**kwargs)
94 def _curve(self, hooke, params):
95 """Get the selected curve.
99 `hooke` is intended to attach the selected curve to the local
100 playlist; the returned curve should not be effected by the
101 state of `hooke`. This is important for reliable
102 :class:`~hooke.command_stack.CommandStack`\s.
104 # HACK? rely on params['curve'] being bound to the local hooke
105 # playlist (i.e. not a copy, as you would get by passing a
106 # curve through the queue). Ugh. Stupid queues. As an
107 # alternative, we could pass lookup information through the
109 return params['curve']
111 def _add_to_command_stack(self, params):
112 """Store the command name and current `params` values in the
113 curve's `.command_stack`.
115 If this would duplicate the command currently on top of the
116 stack, no action is taken. Call early on, or watch out for
117 repeated param processing.
119 Recommended practice is to *not* lock in argument values that
120 are loaded from the plugin's :attr:`.config`.
124 Perhaps we should subclass :meth:`_run` and use :func:`super`,
125 or embed this in :meth:`run` to avoid subclasses calling this
126 method explicitly, with all the tedium and brittality that
127 implies. On the other hand, the current implemtnation allows
128 CurveCommands that don't effect the curve itself
129 (e.g. :class:`GetCommand`) to avoid adding themselves to the
132 if params['stack'] == True:
133 curve = self._curve(hooke=None, params=params)
134 if (len(curve.command_stack) > 0
135 and curve.command_stack[-1].command == self.name
136 and curve.command_stack[-1].arguments == params):
137 pass # no need to place duplicate calls on the stack.
139 curve.command_stack.append(CommandMessage(
140 self.name, dict(params)))
143 class BlockCommand (CurveCommand):
144 """A :class:`CurveCommand` operating on a :class:`~hooke.curve.Data` block.
146 def __init__(self, blocks=None, **kwargs):
148 blocks = [('block', None, 'Name of the data block to act on.')]
150 for name,default,help in blocks:
151 block_args.append(block_argument(name, default, help))
152 self._block_arguments = block_args
153 if 'arguments' not in kwargs:
154 kwargs['arguments'] = []
155 kwargs['arguments'] = block_args + kwargs['arguments']
156 super(BlockCommand, self).__init__(**kwargs)
158 def _block_names(self, hooke, params):
159 curve = self._curve(hooke, params)
160 return [b.info['name'] for b in curve.data]
162 def _block_index(self, hooke, params, name=None):
164 name = self._block_arguments[0].name
165 block_name = params[name]
166 if block_name == None:
167 curve = self._curve(hooke=hooke, params=params)
168 if len(curve.data) == 0:
169 raise Failure('no blocks in %s' % curve)
170 block_name = curve.data[0].info['name']
171 names = self._block_names(hooke=hooke, params=params)
173 return names.index(block_name)
174 except ValueError, e:
175 curve = self._curve(hooke, params)
176 raise Failure('no block named %s in %s (%s): %s'
177 % (block_name, curve, names, e))
179 def _block(self, hooke, params, name=None):
180 # HACK? rely on params['block'] being bound to the local hooke
181 # playlist (i.e. not a copy, as you would get by passing a
182 # block through the queue). Ugh. Stupid queues. As an
183 # alternative, we could pass lookup information through the
185 curve = self._curve(hooke, params)
186 index = self._block_index(hooke, params, name)
187 return curve.data[index]
190 class ColumnAccessCommand (BlockCommand):
191 """A :class:`BlockCommand` accessing a :class:`~hooke.curve.Data`
194 def __init__(self, columns=None, **kwargs):
196 columns = [('column', None, 'Name of the data column to act on.')]
198 for name,default,help in columns:
199 column_args.append(column_argument(name, default, help))
200 self._column_arguments = column_args
201 if 'arguments' not in kwargs:
202 kwargs['arguments'] = []
203 kwargs['arguments'] = column_args + kwargs['arguments']
204 super(ColumnAccessCommand, self).__init__(**kwargs)
206 def _get_column(self, hooke, params, block_name=None, column_name=None):
207 if column_name == None:
208 column_name = self._column_arguments[0].name
209 column_name = params[column_name]
210 if column_name is None:
212 block = self._block(hooke, params, block_name)
213 columns = block.info['columns']
215 column_index = columns.index(column_name)
216 except ValueError, e:
217 raise Failure('%s not in %s (%s): %s'
218 % (column_name, block.info['name'], columns, e))
219 return block[:,column_index]
222 class ColumnAddingCommand (ColumnAccessCommand):
223 """A :class:`ColumnAccessCommand` that also adds columns.
225 def __init__(self, new_columns=None, **kwargs):
226 if new_columns == None:
229 for name,default,help in new_columns:
230 column_args.append(column_argument(name, default, help))
231 self._new_column_arguments = column_args
232 if 'arguments' not in kwargs:
233 kwargs['arguments'] = []
234 kwargs['arguments'] = column_args + kwargs['arguments']
235 super(ColumnAddingCommand, self).__init__(**kwargs)
237 def _get_column(self, hooke, params, block_name=None, column_name=None):
238 if column_name == None and len(self._column_arguments) == 0:
239 column_name = self._new_column_arguments[0].name
240 return super(ColumnAddingCommand, self)._get_column(
241 hooke=hooke, params=params, block_name=block_name,
242 column_name=column_name)
244 def _set_column(self, hooke, params, block_name=None, column_name=None,
246 if column_name == None:
247 column_name = self._column_arguments[0].name
248 column_name = params[column_name]
249 block = self._block(hooke=hooke, params=params, name=block_name)
250 if column_name not in block.info['columns']:
251 new = Data((block.shape[0], block.shape[1]+1), dtype=block.dtype)
252 new.info = copy.deepcopy(block.info)
254 new.info['columns'].append(column_name)
256 block_index = self._block_index(hooke, params, name=block_name)
257 self._curve(hooke, params).data[block_index] = block
258 column_index = block.info['columns'].index(column_name)
259 block[:,column_index] = values
264 class CurvePlugin (Builtin):
266 super(CurvePlugin, self).__init__(name='curve')
268 GetCommand(self), InfoCommand(self), BlockInfoCommand(self),
269 DeltaCommand(self), ExportCommand(self), DifferenceCommand(self),
270 DerivativeCommand(self), PowerSpectrumCommand(self),
271 ScaledColumnAdditionCommand(self), ClearStackCommand(self)]
276 class GetCommand (CurveCommand):
277 """Return a :class:`hooke.curve.Curve`.
279 def __init__(self, plugin):
280 super(GetCommand, self).__init__(
281 name='get curve', help=self.__doc__, plugin=plugin)
283 def _run(self, hooke, inqueue, outqueue, params):
284 outqueue.put(self._curve(hooke, params))
287 class InfoCommand (CurveCommand):
288 """Get selected information about a :class:`hooke.curve.Curve`.
290 def __init__(self, plugin):
292 Argument(name='all', type='bool', default=False, count=1,
293 help='Get all curve information.'),
295 self.fields = ['name', 'path', 'driver', 'note', 'command stack',
296 'blocks', 'block names', 'block sizes']
297 for field in self.fields:
298 args.append(Argument(
299 name=field, type='bool', default=False, count=1,
300 help='Get curve %s' % field))
301 super(InfoCommand, self).__init__(
302 name='curve info', arguments=args,
303 help=self.__doc__, plugin=plugin)
305 def _run(self, hooke, inqueue, outqueue, params):
306 curve = self._curve(hooke, params)
308 for key in self.fields:
309 fields[key] = params[key]
310 if reduce(lambda x,y: x or y, fields.values()) == False:
311 params['all'] = True # No specific fields set, default to 'all'
312 if params['all'] == True:
313 for key in self.fields:
316 for key in self.fields:
317 if fields[key] == True:
318 get = getattr(self, '_get_%s' % key.replace(' ', '_'))
319 lines.append('%s: %s' % (key, get(curve)))
320 outqueue.put('\n'.join(lines))
322 def _get_name(self, curve):
325 def _get_path(self, curve):
328 def _get_driver(self, curve):
331 def _get_note(self, curve):
332 return curve.info.get('note', None)
334 def _get_command_stack(self, curve):
335 return curve.command_stack
337 def _get_blocks(self, curve):
338 return len(curve.data)
340 def _get_block_names(self, curve):
341 return [block.info['name'] for block in curve.data]
343 def _get_block_sizes(self, curve):
344 return [block.shape for block in curve.data]
347 class BlockInfoCommand (BlockCommand):
348 """Get selected information about a :class:`hooke.curve.Curve` data block.
350 def __init__(self, plugin):
351 super(BlockInfoCommand, self).__init__(
352 name='block info', arguments=[
354 name='key', count=-1, optional=False,
355 help='Dot-separted (.) key selection regexp.'),
359 File name for the output (appended).
362 help=self.__doc__, plugin=plugin)
364 def _run(self, hooke, inqueue, outqueue, params):
365 block = self._block(hooke, params)
366 values = {'index': self._block_index(hooke, params)}
367 for key in params['key']:
368 keys = [(0, key.split('.'), block.info)]
370 index,key_stack,info = keys.pop(0)
371 regexp = re.compile(key_stack[index])
373 for k,v in info.items():
376 new_stack = copy.copy(key_stack)
378 if index+1 == len(key_stack):
380 for k in new_stack[:-1]:
384 vals[new_stack[-1]] = v
386 keys.append((index+1, new_stack, v))
389 'no match found for %s (%s) in %s'
390 % (key_stack[index], key, sorted(info.keys())))
391 if params['output'] != None:
392 curve = self._curve(hooke, params)
393 with open(params['output'], 'a') as f:
394 yaml.dump({curve.name:{
396 block.info['name']: values
401 class DeltaCommand (BlockCommand):
402 """Get distance information between two points.
404 With two points A and B, the returned distances are A-B.
406 def __init__(self, plugin):
407 super(DeltaCommand, self).__init__(
410 Argument(name='point', type='point', optional=False, count=2,
412 Indicies of points bounding the selected data.
414 Argument(name='SI', type='bool', default=False,
416 Return distances in SI notation.
419 help=self.__doc__, plugin=plugin)
421 def _run(self, hooke, inqueue, outqueue, params):
422 data = self._block(hooke, params)
423 As = data[params['point'][0],:]
424 Bs = data[params['point'][1],:]
425 ds = [A-B for A,B in zip(As, Bs)]
426 if params['SI'] == False:
427 out = [(name, d) for name,d in zip(data.info['columns'], ds)]
430 for name,d in zip(data.info['columns'], ds):
431 n,units = split_data_label(name)
433 (n, ppSI(value=d, unit=units, decimals=2)))
437 class ExportCommand (BlockCommand):
438 """Export a :class:`hooke.curve.Curve` data block as TAB-delimeted
441 A "#" prefixed header will optionally appear at the beginning of
442 the file naming the columns.
444 def __init__(self, plugin):
445 super(ExportCommand, self).__init__(
448 Argument(name='output', type='file', default='curve.dat',
450 File name for the output data. Defaults to 'curve.dat'
452 Argument(name='header', type='bool', default=True,
454 True if you want the column-naming header line.
457 help=self.__doc__, plugin=plugin)
459 def _run(self, hooke, inqueue, outqueue, params):
460 data = self._block(hooke, params)
462 with open(os.path.expanduser(params['output']), 'w') as f:
463 if params['header'] == True:
464 f.write('# %s \n' % ('\t'.join(data.info['columns'])))
465 numpy.savetxt(f, data, delimiter='\t')
468 class DifferenceCommand (ColumnAddingCommand):
469 """Calculate the difference between two columns of data.
471 The difference is added to block A as a new column.
473 Note that the command will fail if the columns have different
474 lengths, so be careful when differencing columns from different
477 def __init__(self, plugin):
478 super(DifferenceCommand, self).__init__(
482 'Name of block A in A-B. Defaults to the first block'),
484 'Name of block B in A-B. Defaults to matching `block A`.'),
489 Column of data from block A to difference. Defaults to the first column.
493 Column of data from block B to difference. Defaults to matching `column A`.
497 ('output column', None,
499 Name of the new column for storing the difference (without units, defaults to
500 `difference of <block A> <column A> and <block B> <column B>`).
503 help=self.__doc__, plugin=plugin)
505 def _run(self, hooke, inqueue, outqueue, params):
506 self._add_to_command_stack(params)
507 params = self._setup_params(hooke=hooke, params=params)
508 data_A = self._get_column(hooke=hooke, params=params,
509 block_name='block A',
510 column_name='column A')
511 data_B = self._get_column(hooke=hooke, params=params,
512 block_name='block B',
513 column_name='column B')
514 out = data_A - data_B
515 self._set_column(hooke=hooke, params=params,
516 block_name='block A',
517 column_name='output column',
520 def _setup_params(self, hooke, params):
521 curve = self._curve(hooke, params)
522 if params['block A'] == None:
523 params['block A'] = curve.data[0].info['name']
524 if params['block B'] == None:
525 params['block B'] = params['block A']
526 block_A = self._block(hooke, params=params, name='block A')
527 block_B = self._block(hooke, params=params, name='block B')
528 if params['column A'] == None:
529 params['column A'] = block.info['columns'][0]
530 if params['column B'] == None:
531 params['column B'] = params['column A']
532 a_name,a_unit = split_data_label(params['column A'])
533 b_name,b_unit = split_data_label(params['column B'])
535 raise Failure('Unit missmatch: %s != %s' % (a_unit, b_unit))
536 if params['output column'] == None:
537 params['output column'] = join_data_label(
538 'difference of %s %s and %s %s' % (
539 block_A.info['name'], a_name,
540 block_B.info['name'], b_name),
543 params['output column'] = join_data_label(
544 params['output column'], a_unit)
548 class DerivativeCommand (ColumnAddingCommand):
549 """Calculate the derivative (actually, the discrete differentiation)
552 See :func:`hooke.util.calculus.derivative` for implementation
555 def __init__(self, plugin):
556 super(DerivativeCommand, self).__init__(
560 'Column of data block to differentiate with respect to.'),
562 'Column of data block to differentiate.'),
565 ('output column', None,
567 Name of the new column for storing the derivative (without units, defaults to
568 `derivative of <f column> with respect to <x column>`).
572 Argument(name='weights', type='dict', default={-1:-0.5, 1:0.5},
574 Weighting scheme dictionary for finite differencing. Defaults to
575 central differencing.
578 help=self.__doc__, plugin=plugin)
580 def _run(self, hooke, inqueue, outqueue, params):
581 self._add_to_command_stack(params)
582 params = self._setup_params(hooke=hooke, params=params)
583 x_data = self._get_column(hooke=hooke, params=params,
584 column_name='x column')
585 f_data = self._get_column(hooke=hooke, params=params,
586 column_name='f column')
588 x_data=x_data, f_data=f_data, weights=params['weights'])
589 self._set_column(hooke=hooke, params=params,
590 column_name='output column',
593 def _setup_params(self, hooke, params):
594 curve = self._curve(hooke, params)
595 x_name,x_unit = split_data_label(params['x column'])
596 f_name,f_unit = split_data_label(params['f column'])
597 d_unit = '%s/%s' % (f_unit, x_unit)
598 if params['output column'] == None:
599 params['output column'] = join_data_label(
600 'derivative of %s with respect to %s' % (
604 params['output column'] = join_data_label(
605 params['output column'], d_unit)
609 class PowerSpectrumCommand (ColumnAddingCommand):
610 """Calculate the power spectrum of a data column.
612 def __init__(self, plugin):
613 super(PowerSpectrumCommand, self).__init__(
614 name='power spectrum',
616 Argument(name='output block', type='string',
618 Name of the new data block for storing the power spectrum (defaults to
619 `power spectrum of <source block name> <source column name>`).
621 Argument(name='bounds', type='point', optional=True, count=2,
623 Indicies of points bounding the selected data.
625 Argument(name='freq', type='float', default=1.0,
629 Argument(name='freq units', type='string', default='Hz',
631 Units for the sampling frequency.
633 Argument(name='chunk size', type='int', default=2048,
635 Number of samples per chunk. Use a power of two.
637 Argument(name='overlap', type='bool', default=False,
639 If `True`, each chunk overlaps the previous chunk by half its length.
640 Otherwise, the chunks are end-to-end, and not overlapping.
643 help=self.__doc__, plugin=plugin)
645 def _run(self, hooke, inqueue, outqueue, params):
646 self._add_to_command_stack(params)
647 params = self._setup_params(hooke=hooke, params=params)
648 data = self._get_column(hooke=hooke, params=params)
649 bounds = params['bounds']
651 data = data[bounds[0]:bounds[1]]
652 freq_axis,power = unitary_avg_power_spectrum(
653 data, freq=params['freq'],
654 chunk_size=params['chunk size'],
655 overlap=params['overlap'])
656 b = Data((len(freq_axis),2), dtype=data.dtype)
657 b.info['name'] = params['output block']
658 b.info['columns'] = [
659 params['output freq column'],
660 params['output power column'],
662 self._curve(hooke, params).data.append(b)
663 self._set_column(hooke, params, block_name='output block',
664 column_name='output freq column',
666 self._set_column(hooke, params, block_name='output block',
667 column_name='output power column',
671 def _setup_params(self, hooke, params):
672 if params['output block'] in self._block_names(hooke, params):
673 raise Failure('output block %s already exists in %s.'
674 % (params['output block'],
675 self._curve(hooke, params)))
676 data = self._get_column(hooke=hooke, params=params)
677 d_name,d_unit = split_data_label(data.info['name'])
678 if params['output block'] == None:
679 params['output block'] = 'power spectrum of %s %s' % (
680 data.info['name'], params['column'])
681 self.params['output freq column'] = join_data_label(
682 'frequency axis', params['freq units'])
683 self.params['output power column'] = join_data_label(
684 'power density', '%s^2/%s' % (data_units, params['freq units']))
688 class ScaledColumnAdditionCommand (ColumnAddingCommand):
689 """Add one affine transformed column to another: `o=A*i1+B*i2+C`.
691 def __init__(self, plugin):
692 super(ScaledColumnAdditionCommand, self).__init__(
693 name='scaled column addition',
695 ('input column 1', 'input column (m)', """
696 Name of the first column to use as the transform input.
698 ('input column 2', None, """
699 Name of the second column to use as the transform input.
703 ('output column', 'output column (m)', """
704 Name of the column to use as the transform output.
708 Argument(name='scale 1', type='float', default=None,
710 A float value for the first scale constant.
712 Argument(name='scale 1 name', type='string', default=None,
714 The name of the first scale constant in the `.info` dictionary.
716 Argument(name='scale 2', type='float', default=None,
718 A float value for the second scale constant.
720 Argument(name='scale 2 name', type='string', default=None,
722 The name of the second scale constant in the `.info` dictionary.
724 Argument(name='constant', type='float', default=None,
726 A float value for the offset constant.
728 Argument(name='constant name', type='string', default=None,
730 The name of the offset constant in the `.info` dictionary.
733 help=self.__doc__, plugin=plugin)
735 def _run(self, hooke, inqueue, outqueue, params):
736 self._add_to_command_stack(params)
737 i1 = self._get_column(hooke=hooke, params=params,
738 column_name='input column 1')
739 i2 = self._get_column(hooke=hooke, params=params,
740 column_name='input column 2')
745 # what if both i1 and i2 are None?
746 a = self._get_constant(params, i1.info, 'scale 1')
747 b = self._get_constant(params, i2.info, 'scale 2')
748 c = self._get_constant(params, i1.info, 'constant')
749 out = a*i1 + b*i2 + c
750 self._set_column(hooke=hooke, params=params,
751 column_name='output column', values=out)
753 def _get_constant(self, params, info, name):
755 pname = params[name + ' name']
757 if pname is not None:
758 pname_entries = pname.split('|')
760 for entry in pname_entries:
762 if a is None and b is None:
771 class ClearStackCommand (CurveCommand):
772 """Empty a curve's command stack.
774 def __init__(self, plugin):
775 super(ClearStackCommand, self).__init__(
776 name='clear curve command stack',
777 help=self.__doc__, plugin=plugin)
778 i,arg = [(i,arg) for i,arg in enumerate(self.arguments)
779 if arg.name == 'curve'][0]
781 arg.callback = unloaded_current_curve_callback
782 self.arguments[i] = arg
784 def _run(self, hooke, inqueue, outqueue, params):
785 curve = self._curve(hooke, params)
786 curve.command_stack = CommandStack()
789 class OldCruft (object):
791 def do_forcebase(self,args):
795 Measures the difference in force (in pN) between a point and a baseline
796 took as the average between two points.
798 The baseline is fixed once for a given curve and different force measurements,
799 unless the user wants it to be recalculated
801 Syntax: forcebase [rebase]
802 rebase: Forces forcebase to ask again the baseline
803 max: Instead of asking for a point to measure, asks for two points and use
804 the maximum peak in between
806 rebase=False #if true=we select rebase
807 maxpoint=False #if true=we measure the maximum peak
809 plot=self._get_displayed_plot()
810 whatset=1 #fixme: for all sets
811 if 'rebase' in args or (self.basecurrent != self.current.path):
817 print 'Select baseline'
818 self.basepoints=self._measure_N_points(N=2, whatset=whatset)
819 self.basecurrent=self.current.path
822 print 'Select two points'
823 points=self._measure_N_points(N=2, whatset=whatset)
824 boundpoints=[points[0].index, points[1].index]
827 y=min(plot.vectors[whatset][1][boundpoints[0]:boundpoints[1]])
829 print 'Chosen interval not valid. Try picking it again. Did you pick the same point as begin and end of interval?'
831 print 'Select point to measure'
832 points=self._measure_N_points(N=1, whatset=whatset)
833 #whatplot=points[0].dest
834 y=points[0].graph_coords[1]
836 #fixme: code duplication
837 boundaries=[self.basepoints[0].index, self.basepoints[1].index]
839 to_average=plot.vectors[whatset][1][boundaries[0]:boundaries[1]] #y points to average
841 avg=np.mean(to_average)
843 print str(forcebase*(10**12))+' pN'
844 to_dump='forcebase '+self.current.path+' '+str(forcebase*(10**12))+' pN'
845 self.outlet.push(to_dump)
848 def do_slope(self,args):
852 Measures the slope of a delimited chunk on the return trace.
853 The chunk can be delimited either by two manual clicks, or have
854 a fixed width, given as an argument.
856 Syntax: slope [width]
857 The facultative [width] parameter specifies how many
858 points will be considered for the fit. If [width] is
859 specified, only one click will be required.
860 (c) Marco Brucale, Massimo Sandal 2008
863 # Reads the facultative width argument
869 # Decides between the two forms of user input, as per (args)
871 # Gets the Xs of two clicked points as indexes on the current curve vector
872 print 'Click twice to delimit chunk'
873 points=self._measure_N_points(N=2,whatset=1)
875 print 'Click once on the leftmost point of the chunk (i.e.usually the peak)'
876 points=self._measure_N_points(N=1,whatset=1)
878 slope=self._slope(points,fitspan)
880 # Outputs the relevant slope parameter
883 to_dump='slope '+self.current.path+' '+str(slope)
884 self.outlet.push(to_dump)
886 def _slope(self,points,fitspan):
887 # Calls the function linefit_between
888 parameters=[0,0,[],[]]
890 clickedpoints=[points[0].index,points[1].index]
893 clickedpoints=[points[0].index-fitspan,points[0].index]
896 parameters=self.linefit_between(clickedpoints[0],clickedpoints[1])
898 print 'Cannot fit. Did you click twice the same point?'
901 # Outputs the relevant slope parameter
903 print str(parameters[0])
904 to_dump='slope '+self.curve.path+' '+str(parameters[0])
905 self.outlet.push(to_dump)
907 # Makes a vector with the fitted parameters and sends it to the GUI
908 xtoplot=parameters[2]
912 ytoplot.append((x*parameters[0])+parameters[1])
914 clickvector_x, clickvector_y=[], []
916 clickvector_x.append(item.graph_coords[0])
917 clickvector_y.append(item.graph_coords[1])
919 lineplot=self._get_displayed_plot(0) #get topmost displayed plot
921 lineplot.add_set(xtoplot,ytoplot)
922 lineplot.add_set(clickvector_x, clickvector_y)
925 if lineplot.styles==[]:
926 lineplot.styles=[None,None,None,'scatter']
928 lineplot.styles+=[None,'scatter']
929 if lineplot.colors==[]:
930 lineplot.colors=[None,None,'black',None]
932 lineplot.colors+=['black',None]
935 self._send_plot([lineplot])
940 def linefit_between(self,index1,index2,whatset=1):
942 Creates two vectors (xtofit,ytofit) slicing out from the
943 current return trace a portion delimited by the two indexes
945 Then does a least squares linear fit on that slice.
946 Finally returns [0]=the slope, [1]=the intercept of the
947 fitted 1st grade polynomial, and [2,3]=the actual (x,y) vectors
949 (c) Marco Brucale, Massimo Sandal 2008
951 # Translates the indexes into two vectors containing the x,y data to fit
952 xtofit=self.plots[0].vectors[whatset][0][index1:index2]
953 ytofit=self.plots[0].vectors[whatset][1][index1:index2]
955 # Does the actual linear fitting (simple least squares with numpy.polyfit)
957 linefit=np.polyfit(xtofit,ytofit,1)
959 return (linefit[0],linefit[1],xtofit,ytofit)