d58d6605b4e94a623c7e8b29148e8d886edab81e
[hooke.git] / hooke / plugin / curve.py
1 # Copyright (C) 2008-2010 Alberto Gomez-Casado
2 #                         Fabrizio Benedetti
3 #                         Massimo Sandal <devicerandom@gmail.com>
4 #                         W. Trevor King <wking@drexel.edu>
5 #
6 # This file is part of Hooke.
7 #
8 # Hooke is free software: you can redistribute it and/or modify it
9 # under the terms of the GNU Lesser General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
12 #
13 # Hooke is distributed in the hope that it will be useful, but WITHOUT
14 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
16 # Public License for more details.
17 #
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/>.
21
22 """The ``curve`` module provides :class:`CurvePlugin` and several
23 associated :class:`hooke.command.Command`\s for handling
24 :mod:`hooke.curve` classes.
25 """
26
27 import copy
28
29 import numpy
30
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
38 from . import Builtin
39 from .playlist import current_playlist_callback
40
41
42 # Define common or complicated arguments
43
44 def current_curve_callback(hooke, command, argument, value):
45     if value != None:
46         return value
47     playlist = current_playlist_callback(hooke, command, argument, value)
48     curve = playlist.current()
49     if curve == None:
50         raise Failure('No curves in %s' % playlist)
51     return curve
52
53 CurveArgument = Argument(
54     name='curve', type='curve', callback=current_curve_callback,
55     help="""
56 :class:`hooke.curve.Curve` to act on.  Defaults to the current curve
57 of the current playlist.
58 """.strip())
59
60 def _name_argument(name, default, help):
61     """TODO
62     """
63     return Argument(name=name, type='string', default=default, help=help)
64
65 def block_argument(*args, **kwargs):
66     """TODO
67     """
68     return _name_argument(*args, **kwargs)
69
70 def column_argument(*args, **kwargs):
71     """TODO
72     """
73     return _name_argument(*args, **kwargs)
74
75
76 # Define useful command subclasses
77
78 class CurveCommand (Command):
79     """A :class:`~hooke.command.Command` operating on a
80     :class:`~hooke.curve.Curve`.
81     """
82     def __init__(self, **kwargs):
83         if 'arguments' in kwargs:
84             kwargs['arguments'].insert(0, CurveArgument)
85         else:
86             kwargs['arguments'] = [CurveArgument]
87         super(CurveCommand, self).__init__(**kwargs)
88
89     def _curve(self, hooke, params):
90         """Get the selected curve.
91
92         Notes
93         -----
94         `hooke` is intended to attach the selected curve to the local
95         playlist; the returned curve should not be effected by the
96         state of `hooke`.  This is important for reliable
97         :class:`~hooke.command_stack.CommandStack`\s.
98         """
99         # HACK? rely on params['curve'] being bound to the local hooke
100         # playlist (i.e. not a copy, as you would get by passing a
101         # curve through the queue).  Ugh.  Stupid queues.  As an
102         # alternative, we could pass lookup information through the
103         # queue...
104         return params['curve']
105
106     def _add_to_command_stack(self, params):
107         """Store the command name and current `params` values in the
108         curve's `.command_stack`.
109
110         If this would duplicate the command currently on top of the
111         stack, no action is taken.  Call early on, or watch out for
112         repeated param processing.
113
114         Recommended practice is to *not* lock in argument values that
115         are loaded from the plugin's :attr:`.config`.
116
117         Notes
118         -----
119         Perhaps we should subclass :meth:`_run` and use :func:`super`,
120         or embed this in :meth:`run` to avoid subclasses calling this
121         method explicitly, with all the tedium and brittality that
122         implies.  On the other hand, the current implemtnation allows
123         CurveCommands that don't effect the curve itself
124         (e.g. :class:`GetCommand`) to avoid adding themselves to the
125         stack entirely.
126         """
127         if params['stack'] == True:
128             curve = self._curve(hooke=None, params=params)
129             if (len(curve.command_stack) > 0
130                 and curve.command_stack[-1].command == self.name
131                 and curve.command_stack[-1].arguments == params):
132                 pass  # no need to place duplicate calls on the stack.
133             else:
134                 curve.command_stack.append(CommandMessage(
135                         self.name, params))
136
137
138 class BlockCommand (CurveCommand):
139     """A :class:`CurveCommand` operating on a :class:`~hooke.curve.Data` block.
140     """
141     def __init__(self, blocks=None, **kwargs):
142         if blocks == None:
143             blocks = [('block', None, 'Name of the data block to act on.')]
144         block_args = []
145         for name,default,help in blocks:
146             block_args.append(block_argument(name, default, help))
147         self._block_arguments = block_args
148         if 'arguments' not in kwargs:
149             kwargs['arguments'] = []
150         kwargs['arguments'] = block_args + kwargs['arguments']
151         super(BlockCommand, self).__init__(**kwargs)
152
153     def _block_names(self, hooke, params):
154         curve = self._curve(hooke, params)
155         return [b.info['name'] for b in curve.data]
156
157     def _block_index(self, hooke, params, name=None):
158         if name == None:
159             name = self._block_arguments[0].name
160         block_name = params[name]
161         if block_name == None:
162             curve = self._curve(hooke=hooke, params=params)
163             if len(curve.data) == 0:
164                 raise Failure('no blocks in %s' % curve)
165             block_name = curve.data[0].info['name']
166         names = self._block_names(hooke=hooke, params=params)
167         try:
168             return names.index(block_name)
169         except ValueError, e:
170             curve = self._curve(hooke, params)
171             raise Failure('no block named %s in %s (%s): %s'
172                           % (block_name, curve, names, e))
173
174     def _block(self, hooke, params, name=None):
175         # HACK? rely on params['block'] being bound to the local hooke
176         # playlist (i.e. not a copy, as you would get by passing a
177         # block through the queue).  Ugh.  Stupid queues.  As an
178         # alternative, we could pass lookup information through the
179         # queue...
180         curve = self._curve(hooke, params)
181         index = self._block_index(hooke, params, name)
182         return curve.data[index]
183
184
185 class ColumnAccessCommand (BlockCommand):
186     """A :class:`BlockCommand` accessing a :class:`~hooke.curve.Data`
187     block column.
188     """
189     def __init__(self, columns=None, **kwargs):
190         if columns == None:
191             columns = [('column', None, 'Name of the data column to act on.')]
192         column_args = []
193         for name,default,help in columns:
194             column_args.append(column_argument(name, default, help))
195         self._column_arguments = column_args
196         if 'arguments' not in kwargs:
197             kwargs['arguments'] = []
198         kwargs['arguments'] = column_args + kwargs['arguments']
199         super(ColumnAccessCommand, self).__init__(**kwargs)
200
201     def _get_column(self, hooke, params, block_name=None, column_name=None):
202         if column_name == None:
203             column_name = self._column_arguments[0].name
204         column_name = params[column_name]
205         block = self._block(hooke, params, block_name)
206         columns = block.info['columns']
207         try:
208             column_index = columns.index(column_name)
209         except ValueError, e:
210             raise Failure('%s not in %s (%s): %s'
211                           % (column_name, block.info['name'], columns, e))
212         return block[:,column_index]
213
214
215 class ColumnAddingCommand (ColumnAccessCommand):
216     """A :class:`ColumnAccessCommand` that also adds columns.
217     """
218     def __init__(self, new_columns=None, **kwargs):
219         if new_columns == None:
220             new_columns = []
221         column_args = []
222         for name,default,help in new_columns:
223             column_args.append(column_argument(name, default, help))
224         self._new_column_arguments = column_args
225         if 'arguments' not in kwargs:
226             kwargs['arguments'] = []
227         kwargs['arguments'] = column_args + kwargs['arguments']
228         super(ColumnAddingCommand, self).__init__(**kwargs)
229
230     def _get_column(self, hooke, params, block_name=None, column_name=None):
231         if column_name == None and len(self._column_arguments) == 0:
232             column_name = self._new_column_arguments[0].name
233         return super(ColumnAddingCommand, self)._get_column(
234             hooke=hooke, params=params, block_name=block_name,
235             column_name=column_name)
236
237     def _set_column(self, hooke, params, block_name=None, column_name=None,
238                     values=None):
239         if column_name == None:
240             column_name = self._column_arguments[0].name
241         column_name = params[column_name]
242         block = self._block(hooke=hooke, params=params, name=block_name)
243         if column_name not in block.info['columns']:
244             new = Data((block.shape[0], block.shape[1]+1), dtype=block.dtype)
245             new.info = copy.deepcopy(block.info)
246             new[:,:-1] = block
247             new.info['columns'].append(column_name)
248             block = new
249             block_index = self._block_index(hooke, params, name=block_name)
250             self._curve(hooke, params).data[block_index] = block
251         column_index = block.info['columns'].index(column_name)
252         block[:,column_index] = values
253
254
255 # The plugin itself
256
257 class CurvePlugin (Builtin):
258     def __init__(self):
259         super(CurvePlugin, self).__init__(name='curve')
260         self._commands = [
261             GetCommand(self), InfoCommand(self), DeltaCommand(self),
262             ExportCommand(self), DifferenceCommand(self),
263             DerivativeCommand(self), PowerSpectrumCommand(self),
264             ClearStackCommand(self)]
265
266
267 # Define commands
268
269 class GetCommand (CurveCommand):
270     """Return a :class:`hooke.curve.Curve`.
271     """
272     def __init__(self, plugin):
273         super(GetCommand, self).__init__(
274             name='get curve', help=self.__doc__, plugin=plugin)
275
276     def _run(self, hooke, inqueue, outqueue, params):
277         outqueue.put(self._curve(hooke, params))
278
279
280 class InfoCommand (CurveCommand):
281     """Get selected information about a :class:`hooke.curve.Curve`.
282     """
283     def __init__(self, plugin):
284         args = [
285             Argument(name='all', type='bool', default=False, count=1,
286                      help='Get all curve information.'),
287             ]
288         self.fields = ['name', 'path', 'experiment', 'driver', 'filetype', 'note',
289                        'blocks', 'block sizes']
290         for field in self.fields:
291             args.append(Argument(
292                     name=field, type='bool', default=False, count=1,
293                     help='Get curve %s' % field))
294         super(InfoCommand, self).__init__(
295             name='curve info', arguments=args,
296             help=self.__doc__, plugin=plugin)
297
298     def _run(self, hooke, inqueue, outqueue, params):
299         curve = self._curve(hooke, params)
300         fields = {}
301         for key in self.fields:
302             fields[key] = params[key]
303         if reduce(lambda x,y: x and y, fields.values()) == False:
304             params['all'] = True # No specific fields set, default to 'all'
305         if params['all'] == True:
306             for key in self.fields:
307                 fields[key] = True
308         lines = []
309         for key in self.fields:
310             if fields[key] == True:
311                 get = getattr(self, '_get_%s' % key.replace(' ', '_'))
312                 lines.append('%s: %s' % (key, get(curve)))
313         outqueue.put('\n'.join(lines))
314
315     def _get_name(self, curve):
316         return curve.name
317
318     def _get_path(self, curve):
319         return curve.path
320
321     def _get_experiment(self, curve):
322         return curve.info.get('experiment', None)
323
324     def _get_driver(self, curve):
325         return curve.driver
326
327     def _get_filetype(self, curve):
328         return curve.info.get('filetype', None)
329
330     def _get_note(self, curve):
331         return curve.info.get('note', None)
332                               
333     def _get_blocks(self, curve):
334         return len(curve.data)
335
336     def _get_block_sizes(self, curve):
337         return [block.shape for block in curve.data]
338
339
340 class DeltaCommand (BlockCommand):
341     """Get distance information between two points.
342
343     With two points A and B, the returned distances are A-B.
344     """
345     def __init__(self, plugin):
346         super(DeltaCommand, self).__init__(
347             name='delta',
348             arguments=[
349                 Argument(name='point', type='point', optional=False, count=2,
350                          help="""
351 Indicies of points bounding the selected data.
352 """.strip()),
353                 Argument(name='SI', type='bool', default=False,
354                          help="""
355 Return distances in SI notation.
356 """.strip())
357                 ],
358             help=self.__doc__, plugin=plugin)
359
360     def _run(self, hooke, inqueue, outqueue, params):
361         data = self._block(hooke, params)
362         As = data[params['point'][0],:]
363         Bs = data[params['point'][1],:]
364         ds = [A-B for A,B in zip(As, Bs)]
365         if params['SI'] == False:
366             out = [(name, d) for name,d in zip(data.info['columns'], ds)]
367         else:
368             out = []
369             for name,d in zip(data.info['columns'], ds):
370                 n,units = split_data_label(name)
371                 out.append(
372                   (n, ppSI(value=d, unit=units, decimals=2)))
373         outqueue.put(out)
374
375
376 class ExportCommand (BlockCommand):
377     """Export a :class:`hooke.curve.Curve` data block as TAB-delimeted
378     ASCII text.
379
380     A "#" prefixed header will optionally appear at the beginning of
381     the file naming the columns.
382     """
383     def __init__(self, plugin):
384         super(ExportCommand, self).__init__(
385             name='export block',
386             arguments=[
387                 Argument(name='output', type='file', default='curve.dat',
388                          help="""
389 File name for the output data.  Defaults to 'curve.dat'
390 """.strip()),
391                 Argument(name='header', type='bool', default=True,
392                          help="""
393 True if you want the column-naming header line.
394 """.strip()),
395                 ],
396             help=self.__doc__, plugin=plugin)
397
398     def _run(self, hooke, inqueue, outqueue, params):
399         data = self._block(hooke, params)
400
401         with open(params['output'], 'w') as f:
402             if params['header'] == True:
403                 f.write('# %s \n' % ('\t'.join(data.info['columns'])))
404             numpy.savetxt(f, data, delimiter='\t')
405
406
407 class DifferenceCommand (ColumnAddingCommand):
408     """Calculate the difference between two columns of data.
409
410     The difference is added to block A as a new column.
411
412     Note that the command will fail if the columns have different
413     lengths, so be careful when differencing columns from different
414     blocks.
415     """
416     def __init__(self, plugin):
417         super(DifferenceCommand, self).__init__(
418             name='difference',
419             blocks=[
420                 ('block A', None,
421                  'Name of block A in A-B.  Defaults to the first block'),
422                 ('block B', None,
423                  'Name of block B in A-B.  Defaults to matching `block A`.'),
424                 ],
425             columns=[
426                 ('column A', None,
427                  """
428 Column of data from block A to difference.  Defaults to the first column.
429 """.strip()),
430                 ('column B', None,
431                  """
432 Column of data from block B to difference.  Defaults to matching `column A`.
433 """.strip()),
434                 ],
435             new_columns=[
436                 ('output column', None,
437                  """
438 Name of the new column for storing the difference (without units, defaults to
439 `difference of <block A> <column A> and <block B> <column B>`).
440 """.strip()),
441                 ],
442             help=self.__doc__, plugin=plugin)
443
444     def _run(self, hooke, inqueue, outqueue, params):
445         self._add_to_command_stack(params)
446         params = self.__setup_params(hooke=hooke, params=params)
447         data_A = self._get_column(hooke=hooke, params=params,
448                                   block_name='block A',
449                                   column_name='column A')
450         data_B = self._get_column(hooke=hooke, params=params,
451                                   block_name='block B',
452                                   column_name='column B')
453         out = data_A - data_B
454         self._set_column(hooke=hooke, params=params,
455                          block_name='block A',
456                          column_name='output column',
457                          values=out)
458
459     def __setup_params(self, hooke, params):
460         curve = self._curve(hooke, params)
461         if params['block A'] == None:
462             params['block A'] = curve.data[0].info['name']
463         if params['block B'] == None:
464             params['block B'] = params['block A']
465         block_A = self._block(hooke, params=params, name='block A')
466         block_B = self._block(hooke, params=params, name='block B')
467         if params['column A'] == None:
468             params['column A'] = block.info['columns'][0]
469         if params['column B'] == None:
470             params['column B'] = params['column A']
471         a_name,a_unit = split_data_label(params['column A'])
472         b_name,b_unit = split_data_label(params['column B'])
473         if a_unit != b_unit:
474             raise Failure('Unit missmatch: %s != %s' % (a_unit, b_unit))
475         if params['output column'] == None:
476             params['output column'] = join_data_label(
477                 'difference of %s %s and %s %s' % (
478                     block_A.info['name'], a_name,
479                     block_B.info['name'], b_name),
480                 a_unit)
481         else:
482             params['output column'] = join_data_label(
483                 params['output column'], a_unit)
484         return params
485
486
487 class DerivativeCommand (ColumnAddingCommand):
488     """Calculate the derivative (actually, the discrete differentiation)
489     of a data column.
490
491     See :func:`hooke.util.calculus.derivative` for implementation
492     details.
493     """
494     def __init__(self, plugin):
495         super(DerivativeCommand, self).__init__(
496             name='derivative',
497             columns=[
498                 ('x column', None,
499                  'Column of data block to differentiate with respect to.'),
500                 ('f column', None,
501                  'Column of data block to differentiate.'),
502                 ],
503             new_columns=[
504                 ('output column', None,
505                  """
506 Name of the new column for storing the derivative (without units, defaults to
507 `derivative of <f column> with respect to <x column>`).
508 """.strip()),
509                 ],
510             arguments=[
511                 Argument(name='weights', type='dict', default={-1:-0.5, 1:0.5},
512                          help="""
513 Weighting scheme dictionary for finite differencing.  Defaults to
514 central differencing.
515 """.strip()),
516                 ],
517             help=self.__doc__, plugin=plugin)
518
519     def _run(self, hooke, inqueue, outqueue, params):
520         self._add_to_command_stack(params)
521         params = self.__setup_params(hooke=hooke, params=params)
522         x_data = self._get_column(hooke=hooke, params=params,
523                                   column_name='x column')
524         f_data = self._get_column(hooke=hooke, params=params,
525                                   column_name='f column')
526         d = derivative(
527             x_data=x_data, f_data=f_data, weights=params['weights'])
528         self._set_column(hooke=hooke, params=params,
529                          column_name='output column',
530                          values=d)
531
532     def __setup_params(self, hooke, params):
533         curve = self._curve(hooke, params)
534         x_name,x_unit = split_data_label(params['x column'])
535         f_name,f_unit = split_data_label(params['f column'])
536         d_unit = '%s/%s' % (f_unit, x_unit)
537         if params['output column'] == None:
538             params['output column'] = join_data_label(
539                 'derivative of %s with respect to %s' % (
540                     f_name, x_name),
541                 d_unit)
542         else:
543             params['output column'] = join_data_label(
544                 params['output column'], d_unit)
545         return params
546
547
548 class PowerSpectrumCommand (ColumnAddingCommand):
549     """Calculate the power spectrum of a data column.
550     """
551     def __init__(self, plugin):
552         super(PowerSpectrumCommand, self).__init__(
553             name='power spectrum',
554             arguments=[
555                 Argument(name='output block', type='string',
556                          help="""
557 Name of the new data block for storing the power spectrum (defaults to
558 `power spectrum of <source block name> <source column name>`).
559 """.strip()),
560                 Argument(name='bounds', type='point', optional=True, count=2,
561                          help="""
562 Indicies of points bounding the selected data.
563 """.strip()),
564                 Argument(name='freq', type='float', default=1.0,
565                          help="""
566 Sampling frequency.
567 """.strip()),
568                 Argument(name='freq units', type='string', default='Hz',
569                          help="""
570 Units for the sampling frequency.
571 """.strip()),
572                 Argument(name='chunk size', type='int', default=2048,
573                          help="""
574 Number of samples per chunk.  Use a power of two.
575 """.strip()),
576                 Argument(name='overlap', type='bool', default=False,
577                          help="""
578 If `True`, each chunk overlaps the previous chunk by half its length.
579 Otherwise, the chunks are end-to-end, and not overlapping.
580 """.strip()),
581                 ],
582             help=self.__doc__, plugin=plugin)
583
584     def _run(self, hooke, inqueue, outqueue, params):
585         self._add_to_command_stack(params)
586         params = self.__setup_params(hooke=hooke, params=params)
587         data = self._get_column(hooke=hooke, params=params)
588         bounds = params['bounds']
589         if bounds != None:
590             data = data[bounds[0]:bounds[1]]
591         freq_axis,power = unitary_avg_power_spectrum(
592             data, freq=params['freq'],
593             chunk_size=params['chunk size'],
594             overlap=params['overlap'])
595         b = Data((len(freq_axis),2), dtype=data.dtype)
596         b.info['name'] = params['output block']
597         b.info['columns'] = [
598             params['output freq column'],
599             params['output power column'],
600             ]
601         self._curve(hooke, params).data.append(b)
602         self._set_column(hooke, params, block_name='output block',
603                          column_name='output freq column',
604                          values=freq_axis)
605         self._set_column(hooke, params, block_name='output block',
606                          column_name='output power column',
607                          values=power)
608         outqueue.put(b)
609
610     def __setup_params(self, hooke, params):
611         if params['output block'] in self._block_names(hooke, params):
612             raise Failure('output block %s already exists in %s.'
613                           % (params['output block'],
614                              self._curve(hooke, params)))
615         data = self._get_column(hooke=hooke, params=params)
616         d_name,d_unit = split_data_label(data.info['name'])
617         if params['output block'] == None:
618             params['output block'] = 'power spectrum of %s %s' % (
619                 data.info['name'], params['column'])
620         self.params['output freq column'] = join_data_label(
621             'frequency axis', params['freq units'])
622         self.params['output power column'] = join_data_label(
623             'power density', '%s^2/%s' % (data_units, params['freq units']))
624         return params
625
626
627 class ClearStackCommand (CurveCommand):
628     """Empty a curve's command stack.
629     """
630     def __init__(self, plugin):
631         super(ClearStackCommand, self).__init__(
632             name='clear curve command stack',
633             help=self.__doc__, plugin=plugin)
634
635     def _run(self, hooke, inqueue, outqueue, params):
636         curve = self._curve(hooke, params)
637         curve.command_stack = CommandStack()
638
639
640 class OldCruft (object):
641
642     def do_forcebase(self,args):
643         '''
644         FORCEBASE
645         (generalvclamp.py)
646         Measures the difference in force (in pN) between a point and a baseline
647         took as the average between two points.
648
649         The baseline is fixed once for a given curve and different force measurements,
650         unless the user wants it to be recalculated
651         ------------
652         Syntax: forcebase [rebase]
653                 rebase: Forces forcebase to ask again the baseline
654                 max: Instead of asking for a point to measure, asks for two points and use
655                      the maximum peak in between
656         '''
657         rebase=False #if true=we select rebase
658         maxpoint=False #if true=we measure the maximum peak
659
660         plot=self._get_displayed_plot()
661         whatset=1 #fixme: for all sets
662         if 'rebase' in args or (self.basecurrent != self.current.path):
663             rebase=True
664         if 'max' in args:
665             maxpoint=True
666
667         if rebase:
668             print 'Select baseline'
669             self.basepoints=self._measure_N_points(N=2, whatset=whatset)
670             self.basecurrent=self.current.path
671
672         if maxpoint:
673             print 'Select two points'
674             points=self._measure_N_points(N=2, whatset=whatset)
675             boundpoints=[points[0].index, points[1].index]
676             boundpoints.sort()
677             try:
678                 y=min(plot.vectors[whatset][1][boundpoints[0]:boundpoints[1]])
679             except ValueError:
680                 print 'Chosen interval not valid. Try picking it again. Did you pick the same point as begin and end of interval?'
681         else:
682             print 'Select point to measure'
683             points=self._measure_N_points(N=1, whatset=whatset)
684             #whatplot=points[0].dest
685             y=points[0].graph_coords[1]
686
687         #fixme: code duplication
688         boundaries=[self.basepoints[0].index, self.basepoints[1].index]
689         boundaries.sort()
690         to_average=plot.vectors[whatset][1][boundaries[0]:boundaries[1]] #y points to average
691
692         avg=np.mean(to_average)
693         forcebase=abs(y-avg)
694         print str(forcebase*(10**12))+' pN'
695         to_dump='forcebase '+self.current.path+' '+str(forcebase*(10**12))+' pN'
696         self.outlet.push(to_dump)
697
698     #---SLOPE---
699     def do_slope(self,args):
700         '''
701         SLOPE
702         (generalvclamp.py)
703         Measures the slope of a delimited chunk on the return trace.
704         The chunk can be delimited either by two manual clicks, or have
705         a fixed width, given as an argument.
706         ---------------
707         Syntax: slope [width]
708                 The facultative [width] parameter specifies how many
709                 points will be considered for the fit. If [width] is
710                 specified, only one click will be required.
711         (c) Marco Brucale, Massimo Sandal 2008
712         '''
713
714         # Reads the facultative width argument
715         try:
716             fitspan=int(args)
717         except:
718             fitspan=0
719
720         # Decides between the two forms of user input, as per (args)
721         if fitspan == 0:
722             # Gets the Xs of two clicked points as indexes on the current curve vector
723             print 'Click twice to delimit chunk'
724             points=self._measure_N_points(N=2,whatset=1)
725         else:
726             print 'Click once on the leftmost point of the chunk (i.e.usually the peak)'
727             points=self._measure_N_points(N=1,whatset=1)
728             
729         slope=self._slope(points,fitspan)
730
731         # Outputs the relevant slope parameter
732         print 'Slope:'
733         print str(slope)
734         to_dump='slope '+self.current.path+' '+str(slope)
735         self.outlet.push(to_dump)
736
737     def _slope(self,points,fitspan):
738         # Calls the function linefit_between
739         parameters=[0,0,[],[]]
740         try:
741             clickedpoints=[points[0].index,points[1].index]
742             clickedpoints.sort()
743         except:
744             clickedpoints=[points[0].index-fitspan,points[0].index]        
745
746         try:
747             parameters=self.linefit_between(clickedpoints[0],clickedpoints[1])
748         except:
749             print 'Cannot fit. Did you click twice the same point?'
750             return
751              
752         # Outputs the relevant slope parameter
753         print 'Slope:'
754         print str(parameters[0])
755         to_dump='slope '+self.curve.path+' '+str(parameters[0])
756         self.outlet.push(to_dump)
757
758         # Makes a vector with the fitted parameters and sends it to the GUI
759         xtoplot=parameters[2]
760         ytoplot=[]
761         x=0
762         for x in xtoplot:
763             ytoplot.append((x*parameters[0])+parameters[1])
764
765         clickvector_x, clickvector_y=[], []
766         for item in points:
767             clickvector_x.append(item.graph_coords[0])
768             clickvector_y.append(item.graph_coords[1])
769
770         lineplot=self._get_displayed_plot(0) #get topmost displayed plot
771
772         lineplot.add_set(xtoplot,ytoplot)
773         lineplot.add_set(clickvector_x, clickvector_y)
774
775
776         if lineplot.styles==[]:
777             lineplot.styles=[None,None,None,'scatter']
778         else:
779             lineplot.styles+=[None,'scatter']
780         if lineplot.colors==[]:
781             lineplot.colors=[None,None,'black',None]
782         else:
783             lineplot.colors+=['black',None]
784         
785         
786         self._send_plot([lineplot])
787
788         return parameters[0]
789
790
791     def linefit_between(self,index1,index2,whatset=1):
792         '''
793         Creates two vectors (xtofit,ytofit) slicing out from the
794         current return trace a portion delimited by the two indexes
795         given as arguments.
796         Then does a least squares linear fit on that slice.
797         Finally returns [0]=the slope, [1]=the intercept of the
798         fitted 1st grade polynomial, and [2,3]=the actual (x,y) vectors
799         used for the fit.
800         (c) Marco Brucale, Massimo Sandal 2008
801         '''
802         # Translates the indexes into two vectors containing the x,y data to fit
803         xtofit=self.plots[0].vectors[whatset][0][index1:index2]
804         ytofit=self.plots[0].vectors[whatset][1][index1:index2]
805
806         # Does the actual linear fitting (simple least squares with numpy.polyfit)
807         linefit=[]
808         linefit=np.polyfit(xtofit,ytofit,1)
809
810         return (linefit[0],linefit[1],xtofit,ytofit)