From 8aefc576eee45cedbc7cfe009c67bd0ff11e2468 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 Aug 2010 21:37:44 -0400 Subject: [PATCH] Flesh out command stack execution. --- doc/tutorial.txt | 45 +++++-- hooke/command.py | 2 + hooke/command_stack.py | 10 +- hooke/plugin/command_stack.py | 36 +++++- hooke/plugin/curve.py | 34 +++-- hooke/plugin/note.py | 2 +- hooke/plugin/playlist.py | 39 +++--- ....py => apply_command_stack_to_playlist.py} | 79 ++++++++++-- test/command_stack.py | 43 +++++++ test/multiple_curve_analysis.py | 116 ++++++++++++++++++ test/tutorial.py | 10 +- 11 files changed, 362 insertions(+), 54 deletions(-) rename test/{apply_command_stack.py => apply_command_stack_to_playlist.py} (51%) create mode 100644 test/multiple_curve_analysis.py diff --git a/doc/tutorial.txt b/doc/tutorial.txt index 4ae616b..a2ff0cc 100644 --- a/doc/tutorial.txt +++ b/doc/tutorial.txt @@ -327,23 +327,44 @@ capturing a command stack (e.g. to test a complicated command before continuing), you can continue adding to the same stack with ``restart_command_capture.`` +To execute a command stack, run:: + + hooke> execute_command_stack + +To execute a command stack on every curve in a playlist, run:: + + hooke> apply_command_stack_to_playlist + +If you decide that there are commands in a curve's stack that you +don't want, you can clear the stack with:: + + hooke> clear_curve_command_stack + You can also save command stacks to disk (and reload them later, -potentially in a different Hooke session). +potentially in a different Hooke session).:: hooke> save_command_stack --output my_stack hooke> load_command_stack --input my_stack -Multiple curve fitting and measuring -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. todo:: Update multiple curve fitting tutorial section. - -You can cycle through all your current playlist obtaining WLC fit, FJC -fit, rupture force and slope (loading rate) information from each -curve using the ``multifit`` command. The collected data can be saved -in a text file for further analysis in your favourite spreadsheet or -statistical program. If you want to check your parameters on the -current curve before fitting all the files in your playlist, use -``multifit justone``. See the ``multifit`` help for more options. +Multiple curve analysis +~~~~~~~~~~~~~~~~~~~~~~~ + +You can analyze multiple curves by combining `Worm like chain and +freely jointed chain fitting`_ and `Command stacks`_.:: + + hooke> start_command_capture + hooke> zero_surface_contact_point --block retract + hooke> flat_filter_peaks --block retract --min_points 1 + hooke> zero_surface_contact_point --block retract + ... --ignore_after_last_peak_info_name 'flat filter peaks' + hooke> convert_distance_to_force --block retract + ... --deflection_column 'surface deflection (m)' + hooke> remove_cantilever_from_extension --block retract + hooke> flat_peaks_to_polymer_peaks --block retract + hooke> polymer_fit_peaks --block retract + hooke> export_block --block retract --output myblock.dat + hooke> stop_command_capture + hooke> apply_command_stack_to_playlist Fast curve reviewing and saving ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/hooke/command.py b/hooke/command.py index c694bff..d88155c 100644 --- a/hooke/command.py +++ b/hooke/command.py @@ -109,6 +109,8 @@ class Command (object): self.arguments = [ Argument(name='help', type='bool', default=False, count=1, help='Print a help message.'), + Argument(name='stack', type='bool', default=True, count=1, + help='Add this command to appropriate command stacks.'), ] + arguments self._help = help self.plugin = plugin diff --git a/hooke/command_stack.py b/hooke/command_stack.py index c116473..17a633b 100644 --- a/hooke/command_stack.py +++ b/hooke/command_stack.py @@ -86,7 +86,7 @@ class CommandStack (list): >>> print [repr(cm) for cm in c] [] """ - def execute(self, hooke): + def execute(self, hooke, stack=False): """Execute a stack of commands. See Also @@ -96,7 +96,7 @@ class CommandStack (list): for command_message in self: if self.filter(hooke, command_message) == True: self.execute_command( - hooke=hooke, command_message=command_message) + hooke=hooke, command_message=command_message, stack=stack) def filter(self, hooke, command_message): """Return `True` to execute `command_message`, `False` otherwise. @@ -105,9 +105,11 @@ class CommandStack (list): """ return True - def execute_command(self, hooke, command_message): + def execute_command(self, hooke, command_message, stack=False): + arguments = dict(command_message.arguments) + arguments['stack'] = stack hooke.run_command(command=command_message.command, - arguments=command_message.arguments) + arguments=arguments) def clear(self): while len(self) > 0: diff --git a/hooke/plugin/command_stack.py b/hooke/plugin/command_stack.py index 3b81c24..8a3ecb8 100644 --- a/hooke/plugin/command_stack.py +++ b/hooke/plugin/command_stack.py @@ -39,6 +39,11 @@ from . import Builtin class CommandStackCommand (Command): """Subclass to avoid pushing control commands to the stack. """ + def __init__(self, *args, **kwargs): + super(CommandStackCommand, self).__init__(*args, **kwargs) + stack = [a for a in self.arguments if a.name == 'stack'][0] + stack.default = False + def _set_state(self, state): try: self.plugin.set_state(state) @@ -102,7 +107,7 @@ class CommandStackPlugin (Builtin): StartCaptureCommand(self), StopCaptureCommand(self), ReStartCaptureCommand(self), PopCommand(self), GetCommand(self), GetStateCommand(self), - SaveCommand(self), LoadCommand(self), + SaveCommand(self), LoadCommand(self), ExecuteCommand(self), ] self._settings = [ Setting(section=self.setting_section, help=self.__doc__), @@ -261,3 +266,32 @@ File name for the input command stack. params['input'] = os.path.join( self.plugin.config['path'], params['input']) return params + + +class ExecuteCommand (Command): + """Execute a :class:`~hooke.command_stack.CommandStack`. + """ + def __init__(self, plugin): + super(ExecuteCommand, self).__init__( + name='execute command stack', + arguments=[ + Argument(name='commands', type='command stack', + help=""" +Command stack to apply to each curve. Defaults to the plugin's +current stack. +""".strip()), + ], + help=self.__doc__, plugin=plugin) + stack = [a for a in self.arguments if a.name == 'stack'][0] + stack.default = False + + def _run(self, hooke, inqueue, outqueue, params): + params = self.__setup_params(hooke=hooke, params=params) + if len(params['commands']) == 0: + return + params['commands'].execute(hooke=hooke, stack=params['stack']) + + def __setup_params(self, hooke, params): + if params['commands'] == None: + params['commands'] = self.plugin.command_stack + return params diff --git a/hooke/plugin/curve.py b/hooke/plugin/curve.py index e6d5ce3..d73a67f 100644 --- a/hooke/plugin/curve.py +++ b/hooke/plugin/curve.py @@ -29,6 +29,7 @@ import copy import numpy from ..command import Command, Argument, Failure +from ..command_stack import CommandStack from ..curve import Data from ..engine import CommandMessage from ..util.calculus import derivative @@ -123,14 +124,15 @@ class CurveCommand (Command): (e.g. :class:`GetCommand`) to avoid adding themselves to the stack entirely. """ - curve = self._curve(hooke=None, params=params) - if (len(curve.command_stack) > 0 - and curve.command_stack[-1].command == self.name - and curve.command_stack[-1].arguments == params): - pass # no need to place duplicate calls on the stack. - else: - curve.command_stack.append(CommandMessage( - self.name, params)) + if params['stack'] == True: + curve = self._curve(hooke=None, params=params) + if (len(curve.command_stack) > 0 + and curve.command_stack[-1].command == self.name + and curve.command_stack[-1].arguments == params): + pass # no need to place duplicate calls on the stack. + else: + curve.command_stack.append(CommandMessage( + self.name, params)) class BlockCommand (CurveCommand): @@ -258,7 +260,8 @@ class CurvePlugin (Builtin): self._commands = [ GetCommand(self), InfoCommand(self), DeltaCommand(self), ExportCommand(self), DifferenceCommand(self), - DerivativeCommand(self), PowerSpectrumCommand(self)] + DerivativeCommand(self), PowerSpectrumCommand(self), + ClearStackCommand(self)] # Define commands @@ -621,6 +624,19 @@ Otherwise, the chunks are end-to-end, and not overlapping. return params +class ClearStackCommand (CurveCommand): + """Empty a curve's command stack. + """ + def __init__(self, plugin): + super(ClearStackCommand, self).__init__( + name='clear curve command stack', + help=self.__doc__, plugin=plugin) + + def _run(self, hooke, inqueue, outqueue, params): + curve = self._curve(hooke, params) + curve.command_stack = CommandStack() + + class OldCruft (object): def do_forcebase(self,args): diff --git a/hooke/plugin/note.py b/hooke/plugin/note.py index b982075..0d0bada 100644 --- a/hooke/plugin/note.py +++ b/hooke/plugin/note.py @@ -77,7 +77,7 @@ Target object for the note. Defaults to the current curve. help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): - outqueue.put(params['target'].info['note']) + outqueue.put(params['target'].info.get('note', None)) class NoteFilterCommand (FilterCommand): diff --git a/hooke/plugin/playlist.py b/hooke/plugin/playlist.py index 53fc28a..fbd30fa 100644 --- a/hooke/plugin/playlist.py +++ b/hooke/plugin/playlist.py @@ -26,8 +26,9 @@ import logging import os.path from ..command import Command, Argument, Failure -from ..playlist import FilePlaylist from ..curve import NotRecognized +from ..playlist import FilePlaylist +from ..util.itertools import reverse_enumerate from . import Builtin @@ -39,7 +40,7 @@ class PlaylistPlugin (Builtin): GetCommand(self), IndexCommand(self), CurveListCommand(self), SaveCommand(self), LoadCommand(self), AddCommand(self), AddGlobCommand(self), - RemoveCommand(self), ApplyCommandStack(self), + RemoveCommand(self), ApplyCommand(self), FilterCommand(self), ] @@ -329,19 +330,20 @@ Index of target curve. self._playlist(hooke, params).jump(params.index()) -class ApplyCommandStack (PlaylistCommand): +class ApplyCommand (PlaylistCommand): """Apply a :class:`~hooke.command_stack.CommandStack` to each curve in a playlist. TODO: discuss `evaluate`. """ def __init__(self, plugin): - super(ApplyCommandStack, self).__init__( - name='apply command stack', + super(ApplyCommand, self).__init__( + name='apply command stack to playlist', arguments=[ - Argument(name='commands', type='command stack', optional=False, + Argument(name='commands', type='command stack', help=""" -Command stack to apply to each curve. +Command stack to apply to each curve. Defaults to the `command_stack` +plugin's current stack. """.strip()), Argument(name='evaluate', type='bool', default=False, help=""" @@ -351,18 +353,27 @@ Evaluate the applied command stack immediately. help=self.__doc__, plugin=plugin) def _run(self, hooke, inqueue, outqueue, params): - if len(params['commands']) == 0: - return + params = self.__setup_params(hooke=hooke, params=params) p = self._playlist(hooke, params) if params['evaluate'] == True: + exec_cmd = hooke.command_by_name['execute command stack'] for curve in p.items(): - for command in params['commands']: - curve.command_stack.execute_command(hooke, command) - curve.command_stack.append(command) + hooke.run_command(exec_cmd.name, + {'commands':params['commands'], + 'stack':True}) else: for curve in p: - curve.command_stack.extend(params['commands']) - curve.unload() # force command stack execution on next access. + for command in params['commands']: + curve.command_stack.append(command) + curve.set_hooke(hooke) + curve.unload() + + def __setup_params(self, hooke, params): + if params['commands'] == None: + cstack_plugin = [p for p in hooke.plugins + if p.name == 'command_stack'][0] + params['commands'] = cstack_plugin.command_stack + return params class FilterCommand (PlaylistAddingCommand, PlaylistCommand): diff --git a/test/apply_command_stack.py b/test/apply_command_stack_to_playlist.py similarity index 51% rename from test/apply_command_stack.py rename to test/apply_command_stack_to_playlist.py index e6a659e..483d032 100644 --- a/test/apply_command_stack.py +++ b/test/apply_command_stack_to_playlist.py @@ -42,40 +42,97 @@ engine message from load playlist (): ... CommandMessage('zero surface contact point'), ... ]) -Test `apply command stack`. +Test `apply command stack to playlist`. ->>> h.run_command('apply command stack', +>>> h.run_command('apply command stack to playlist', ... {'commands': stack, 'evaluate': True}) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_UDIFF -engine running internal , ], evaluate: True}> loading curve 20071120a_i27_t33.100 with driver ... -engine running internal +engine running internal +engine running internal engine message from get curve (): engine message from get curve (): -engine running internal +engine running internal engine message from zero surface contact point (): {...} engine message from zero surface contact point (): +engine message from execute command stack (): loading curve 20071120a_i27_t33.101 with driver ... -engine running internal +engine running internal +engine running internal engine message from get curve (): engine message from get curve (): -engine running internal +engine running internal engine message from zero surface contact point (): {...} engine message from zero surface contact point (): +engine message from execute command stack (): loading curve 20071120a_i27_t33.102 with driver ... + ... loading curve 20071120a_i27_t33.199 with driver ... -engine running internal +engine running internal +engine running internal engine message from get curve (): engine message from get curve (): -engine running internal +engine running internal engine message from zero surface contact point (): {...} engine message from zero surface contact point (): +engine message from execute command stack (): loading curve 0x06130001 with driver ... unloading curve 20071120a_i27_t33.100 -engine running internal +engine running internal , + ], + curve: ...}> +engine running internal +... +engine message from apply command stack to playlist (): +>>> curve = h.playlists.current().current( +... ) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE +loading curve 20071120a_i27_t33.100 with driver ... +engine running internal , ...}> +engine message from zero surface contact point (): {...} +engine message from zero surface contact point (): +unloading curve 20071120a_i27_t33.102 +>>> curve + +>>> curve.command_stack # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE +[, ...}>] + +Test `apply command stack to playlist` without evaluating. + +>>> stack = CommandStack([ +... CommandMessage('flat_filter_peaks --block retract --min_points 1'), +... ]) +>>> h.run_command('apply command stack to playlist', +... {'commands': stack, 'evaluate': False} +... ) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_UDIFF +engine running internal + +unloading curve 20071120a_i27_t33.100 +unloading curve 20071120a_i27_t33.101 +unloading curve 20071120a_i27_t33.102 ... -engine message from apply command stack (): +unloading curve 20071120a_i27_t33.199 +unloading curve 0x06130001 +unloading curve 0x07200000 +engine message from apply command stack to playlist + (): +>>> for c in curve.command_stack: +... print c # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_UDIFF +...}> + + +Test `clear curve command stack`. + +>>> h.run_command('clear curve command stack', arguments={}) +engine running internal +engine message from clear curve command stack (): +>>> curve.command_stack +[] """ diff --git a/test/command_stack.py b/test/command_stack.py index 48ef4d1..a706d67 100644 --- a/test/command_stack.py +++ b/test/command_stack.py @@ -17,6 +17,8 @@ # . """ +>>> import logging +>>> import sys >>> from hooke.hooke import Hooke, HookeRunner >>> h = Hooke() >>> r = HookeRunner() @@ -158,4 +160,45 @@ Success [] Success + +Building command stacks is fun, but its useless if you can't execute +them. First, lets repopulate the in-memory stack. + +>>> h = r.run_lines(h, ['start_command_capture', +... 'debug --attribute config', +... 'version', +... 'stop_command_capture'] +... ) # doctest: +REPORT_UDIFF +Success + +Success + +Success + +Success + + +Setup logging so we can check command output in the doctest. + +>>> log = logging.getLogger('hooke') +>>> stdout_handler = logging.StreamHandler(sys.stdout) +>>> log.addHandler(stdout_handler) + +Execute the stack. We use `h.run_command` because `sys.stdout` is +replaced by a `doctest._SpoofOut`, and doctest has no way to collect +those results from `run_lines`'s engine subprocess. + +>>> h.run_command('execute command stack', arguments={} +... ) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_UDIFF +engine running internal +engine running internal +engine message from debug (): + +engine message from debug (): +engine running internal +engine message from version (): Hooke 1.0.0.alpha (Ninken) +---- +... +engine message from version (): +engine message from execute command stack (): """ diff --git a/test/multiple_curve_analysis.py b/test/multiple_curve_analysis.py new file mode 100644 index 0000000..d7c82c6 --- /dev/null +++ b/test/multiple_curve_analysis.py @@ -0,0 +1,116 @@ +# Copyright (C) 2010 W. Trevor King +# +# This file is part of Hooke. +# +# Hooke is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# Hooke is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with Hooke. If not, see +# . + +""" +>>> from hooke.hooke import Hooke, HookeRunner +>>> h = Hooke() +>>> r = HookeRunner() + +Setup a playlist to act on. + +>>> h = r.run_lines(h, ['load_playlist test/data/vclamp_picoforce/playlist']) + +Success + + +Build the command stack. + +>>> h = r.run_lines(h, [ +... 'start_command_capture', +... 'zero_surface_contact_point --block retract', +... 'flat_filter_peaks --block retract --min_points 1', +... 'zero_surface_contact_point --block retract --ignore_after_last_peak_info_name "flat filter peaks"', +... 'convert_distance_to_force --block retract --deflection_column "surface deflection (m)"', +... 'remove_cantilever_from_extension --block retract', +... 'flat_peaks_to_polymer_peaks --block retract', +... 'polymer_fit_peaks --block retract', +... 'stop_command_capture', +... ]) # doctest: +REPORT_UDIFF +Success + +Success + +Success + +Success + +Success + +Success + +Success + +Success + +Success + + +Apply the command stack. + +>>> h = r.run_lines(h, ['apply_command_stack_to_playlist']) +Success + + +Verify successful application. + +>>> curve = h.playlists.current().current() +>>> curve + +>>> for c in curve.command_stack: +... print c # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_UDIFF + + + + + + + +>>> for c in curve.data[-1].info['columns']: +... print c # doctest: +REPORT_UDIFF +z piezo (m) +deflection (m) +surface distance (m) +surface deflection (m) +flat filter peaks (m) +deflection (N) +cantilever adjusted extension (m) +polymer peak 0 (N) +>>> h.playlists.current().next() +>>> curve = h.playlists.current().current() +>>> curve + +>>> for c in curve.command_stack: +... print c # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE, +REPORT_UDIFF + + + + + + + +>>> for c in curve.data[-1].info['columns']: +... print c # doctest: +REPORT_UDIFF +z piezo (m) +deflection (m) +surface distance (m) +surface deflection (m) +flat filter peaks (m) +deflection (N) +cantilever adjusted extension (m) +polymer peak 0 (N) +""" diff --git a/test/tutorial.py b/test/tutorial.py index 6a6ea6c..7c115d6 100644 --- a/test/tutorial.py +++ b/test/tutorial.py @@ -38,6 +38,7 @@ Command: load_playlist Arguments: help BOOL (bool) Print a help message. +stack BOOL (bool) Add this command to appropriate command stacks. output_playlist STRING (string) Name of the new playlist (defaults to an auto-generated name). input FILE (file) File name for the input playlist. @@ -69,6 +70,7 @@ hooke Success >>> h = r.run_lines(h, ['new_playlist --output_playlist mylist']) + Success >>> h = r.run_lines(h, ['jump_to_playlist -- -1']) @@ -233,9 +235,13 @@ See :file:`delta.py`. *Command stacks* -See :file:`command_stack.py`and :file:`command_stack_save_load.py`. +See :file:`command_stack.py`, +:file:`apply_command_stack_to_playlist.py`, and +:file:`command_stack_save_load.py`. + +*Multiple curve analysis* -*Multiple curve fitting and measuring* +See :file:`multiple_curve_analysis`. *Fast curve reviewing and saving* -- 2.26.2