Add hooke.plugin.curve (moved from hooke.plugin.curvetools)
authorW. Trevor King <wking@drexel.edu>
Sun, 16 May 2010 14:49:56 +0000 (10:49 -0400)
committerW. Trevor King <wking@drexel.edu>
Sun, 16 May 2010 14:49:56 +0000 (10:49 -0400)
Provides 'curve info' and 'export' commands.  Added test.curve_info
to test the 'curve info' command, but it currently fails due to
unimplemnted Curve loading.

Also:
 * Minor docstring cleanups in hooke.plugin.playlist
 * Moved some functions to hooke.plugin.generalvclamp.  I'm not clear
   on Experiment-specific Curve interpretation yet, but these
   functions (which will eventually be Commands) seem better placed
   here.
   * fit_interval_nm
   * find_current_peak
   * pickup_contact_point
   * baseline_points

hooke/plugin/__init__.py
hooke/plugin/curve.py [new file with mode: 0644]
hooke/plugin/curvetools.py [deleted file]
hooke/plugin/generalvclamp.py
hooke/plugin/playlist.py
test/curve_info.py [new file with mode: 0644]

index 075cdffa0e063c7437a09a666e2f96967e6327dc..89a23b248e750b0bf8220dd2296a9c7e622d7fe1 100644 (file)
@@ -57,6 +57,7 @@ default.  TODO: autodiscovery
 
 BUILTIN_MODULES = [
     'config',
+    'curve',
     'debug',
     'note',
     'playlist',
diff --git a/hooke/plugin/curve.py b/hooke/plugin/curve.py
new file mode 100644 (file)
index 0000000..1e0ad06
--- /dev/null
@@ -0,0 +1,149 @@
+# Copyright (C) 2010 Fibrin's Benedetti
+#                    W. Trevor King <wking@drexel.edu>
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+"""The ``curve`` module provides :class:`CurvePlugin` and several
+associated :class:`hooke.command.Command`\s for handling
+:mod:`hooke.curve` classes.
+"""
+
+from ..command import Command, Argument, Failure
+from ..plugin import Builtin
+from ..plugin.playlist import current_playlist_callback
+
+
+class CurvePlugin (Builtin):
+    def __init__(self):
+        super(CurvePlugin, self).__init__(name='curve')
+
+    def commands(self):
+        return [InfoCommand(), ]
+
+
+# Define common or complicated arguments
+
+def current_curve_callback(hooke, command, argument, value):
+    if value != None:
+        return value
+    playlist = current_playlist_callback(hooke, command, argument, value)
+    curve = playlist.current()
+    if curve == None:
+        raise Failure('No curves in %s' % playlist)
+    return curve
+
+CurveArgument = Argument(
+    name='curve', type='curve', callback=current_curve_callback,
+    help="""
+:class:`hooke.curve.Curve` to act on.  Defaults to the current curve
+of the current playlist.
+""".strip())
+
+
+# Define commands
+
+class InfoCommand (Command):
+    """Print selected information about a :class:`hooke.curve.Curve`.
+    """
+    def __init__(self):
+        args = [
+            CurveArgument,                    
+            Argument(name='all', type='bool', default=False, count=1,
+                     help='Print all curve information.'),
+            ]
+        self.fields = ['name', 'path', 'experiment', 'driver', 'filetype', 'note',
+                       'blocks', 'block sizes']
+        for field in self.fields:
+            args.append(Argument(
+                    name=field, type='bool', default=False, count=1,
+                    help='Print curve %s' % field))
+        super(InfoCommand, self).__init__(
+            name='curve info', arguments=args, help=self.__doc__)
+
+    def _run(self, hooke, inqueue, outqueue, params):
+        fields = {}
+        for key in self.fields:
+            fields[key] = params[key]
+        if reduce(lambda x,y: x and y, fields.values()) == False:
+            params['all'] = True # No specific fields set, default to 'all'
+        if params['all'] == True:
+            for key in self.fields:
+                fields[key] = True
+        lines = []
+        for key in self.fields:
+            if fields[key] == True:
+                get = getattr(self, '_get_%s' % key.replace(' ', '_'))
+                lines.append('%s: %s' % (key, get(params['curve'])))
+        outqueue.put('\n'.join(lines))
+
+    def _get_name(self, curve):
+        return curve.name
+
+    def _get_path(self, curve):
+        return curve.path
+
+    def _get_experiment(self, curve):
+        return curve.info.get('experiment', None)
+
+    def _get_driver(self, curve):
+        return curve.driver
+
+    def _get_filetype(self, curve):
+        return curve.info.get('filetype', None)
+
+    def _get_note(self, curve):
+        return curve.info.get('note', None)
+                              
+    def _get_blocks(self, curve):
+        return len(curve.data)
+
+    def _get_block_sizes(self, curve):
+        return [block.shape for block in curve.data]
+
+
+class ExportCommand (Command):
+    """Export a :class:`hooke.curve.Curve` data block as TAB-delimeted
+    ASCII text.
+    """
+    def __init__(self):
+        self.fields = ['name', 'path', 'experiment', 'driver', 'filetype', 'note',
+                       'blocks', 'block sizes']
+        for field in self.fields:
+            args.append(Argument(
+                    name=field, type='bool', default=False, count=1,
+                    help='Print curve %s' % field))
+        super(InfoCommand, self).__init__(
+            name='curve info',
+            arguments=[
+                CurveArgument,
+                Argument(name='block', aliases=['set'], type='int', default=0,
+                    help="""
+Data block to save.  For an approach/retract force curve, `0` selects
+the approacing curve and `1` selects the retracting curve.
+""".strip()),
+                Argument(name='output', type='file', default='curve.dat',
+                         help="""
+File name for the output data.  Defaults to 'curve.dat'
+""".strip()),
+                ],
+            help=self.__doc__)
+
+    def _run(self, hooke, inqueue, outqueue, params):
+        data = params['curve'].data[params['index']]
+        f = open(params['output'], 'w')
+        data.tofile(f, sep='\t')
+        f.close()
diff --git a/hooke/plugin/curvetools.py b/hooke/plugin/curvetools.py
deleted file mode 100644 (file)
index 6fc2f3d..0000000
+++ /dev/null
@@ -1,210 +0,0 @@
-# Copyright (C) 2010 Fabrizio Benedetti
-#                    W. Trevor King <wking@drexel.edu>
-#
-# 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
-# <http://www.gnu.org/licenses/>.
-
-from ..libhooke import WX_GOOD, ClickedPoint
-
-import wxversion
-wxversion.select(WX_GOOD)
-from wx import PostEvent
-import numpy as np
-import scipy as sp
-import copy
-import os.path
-import time
-
-
-class curvetoolsCommands:
-
-      def fit_interval_nm(self,start_index,plot,nm,backwards):
-         '''
-         Calculates the number of points to fit, given a fit interval in nm
-         start_index: index of point
-         plot: plot to use
-         backwards: if true, finds a point backwards.
-         '''
-         whatset=1 #FIXME: should be decidable
-         x_vect=plot.vectors[1][0]
-         
-         c=0
-         i=start_index
-         start=x_vect[start_index]
-         maxlen=len(x_vect)
-         while abs(x_vect[i]-x_vect[start_index])*(10**9) < nm:
-             if i==0 or i==maxlen-1: #we reached boundaries of vector!
-                 return c
-             
-             if backwards:
-                 i-=1
-             else:
-                 i+=1
-             c+=1
-         return c
-
-
-
-      def find_current_peaks(self,noflatten, a=True, maxpeak=True):
-           #Find peaks.
-           if a==True:
-                 a=self.convfilt_config['mindeviation']
-           try:
-                 abs_devs=float(a)
-           except:
-                 print "Bad input, using default."
-                 abs_devs=self.convfilt_config['mindeviation']
-
-           defplot=self.current.curve.default_plots()[0]
-           if not noflatten:
-               flatten=self._find_plotmanip('flatten') #Extract flatten plotmanip
-               defplot=flatten(defplot, self.current, customvalue=1) #Flatten curve before feeding it to has_peaks
-           pk_location,peak_size=self.has_peaks(defplot, abs_devs, maxpeak)
-           return pk_location, peak_size
-
-
-      def pickup_contact_point(self,N=1,whatset=1):
-           '''macro to pick up the contact point by clicking'''
-           contact_point=self._measure_N_points(N=1, whatset=1)[0]
-           contact_point_index=contact_point.index
-           self.wlccontact_point=contact_point
-           self.wlccontact_index=contact_point.index
-           self.wlccurrent=self.current.path
-           return contact_point, contact_point_index
-
-
-
-      def baseline_points(self,peak_location, displayed_plot):
-            clicks=self.config['baseline_clicks']
-            if clicks==0:
-                self.basepoints=[]
-                base_index_0=peak_location[-1]+self.fit_interval_nm(peak_location[-1], displayed_plot, self.config['auto_right_baseline'],False)
-                self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_0))
-                base_index_1=self.basepoints[0].index+self.fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'],False)
-                self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1))
-            elif clicks>0:
-                print 'Select baseline'
-                if clicks==1:
-                    self.basepoints=self._measure_N_points(N=1, whatset=1)
-                    base_index_1=self.basepoints[0].index+self.fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'], False)
-                    self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1))
-                else:
-                    self.basepoints=self._measure_N_points(N=2, whatset=1)
-            
-            self.basecurrent=self.current.path
-            return self.basepoints
-
-
-
-class InfoCommand (Command):
-    """Execute a system command and report the output.
-    """
-    def __init__(self):
-        super(SystemCommand, self).__init__(
-            name='system',
-            arguments=[
-                Argument(
-    name='command', type='string', optional=False, count=-1,
-    help="""
-Command line to execute.
-""".strip())
-],
-            help=self.__doc__)
-
-    def _run(self, hooke, inqueue, outqueue, params):
-        os.system(params['command'])
-
-    def do_info(self,args):
-        '''
-        INFO
-        ----
-        Returns informations about the current curve.
-        '''
-        print 'Path: ',self.current.path
-        print 'Experiment: ',self.current.curve.experiment
-        print 'Filetype: ',self.current.curve.filetype
-        for plot in self.current.curve.default_plots():
-            for set in plot.vectors:
-                lengths=[len(item) for item in set]
-                print 'Data set size: ',lengths
-
-
-    def help_current(self):
-        print '''
-CURRENT
-Prints the current curve path.
-------
-Syntax: current
-        '''
-    def do_current(self,args):
-        print self.current.path
-
-
-    def help_txt(self):
-        print '''
-TXT
-Saves the current curve as a text file
-Columns are, in order:
-X1 , Y1 , X2 , Y2 , X3 , Y3 ...
-
--------------
-Syntax: txt [filename] {plot to export}
-        '''
-    def do_txt(self,args):
-
-        def transposed2(lists, defval=0):
-            '''
-            transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing
-            elements
-            (by Zoran Isailovski on the Python Cookbook online)
-            '''
-            if not lists: return []
-            return map(lambda *row: [elem or defval for elem in row], *lists)
-
-        whichplot=0
-        args=args.split()
-        if len(args)==0:
-            filename=linp.safeinput('Filename?',[self.current.path+'.txt'])
-        else:
-            filename=linp.checkalphainput(args[0],self.current.path+'.txt',[])
-            try:
-                whichplot=int(args[1])
-            except:
-                pass
-
-       try:
-            outofplot=self.plots[whichplot].vectors
-        except:
-            print "Plot index out of range."
-           return 0
-
-        columns=[]     
-        for dataset in self.plots[whichplot].vectors:
-            for i in range(0,len(dataset)):
-                columns.append([])
-                for value in dataset[i]:
-                    columns[-1].append(str(value))
-
-        rows=transposed2(columns, 'nan')
-        rows=[' , '.join(item) for item in rows]
-        text='\n'.join(rows)
-
-        txtfile=open(filename,'w+')
-        #Save units of measure in header
-        txtfile.write('X:'+self.plots[whichplot].units[0]+'\n')
-        txtfile.write('Y:'+self.plots[whichplot].units[1]+'\n')
-        txtfile.write(text)
-        txtfile.close()
index 9c0593e925940f8f845c59ba17c9db0a30f64005..a6721430167442a5133392b046969f8abaadd433 100644 (file)
@@ -356,3 +356,80 @@ class generalvclampCommands(object):
         linefit=np.polyfit(xtofit,ytofit,1)
 
         return (linefit[0],linefit[1],xtofit,ytofit)
+
+
+      def fit_interval_nm(self,start_index,plot,nm,backwards):
+          '''
+          Calculates the number of points to fit, given a fit interval in nm
+          start_index: index of point
+          plot: plot to use
+          backwards: if true, finds a point backwards.
+          '''
+          whatset=1 #FIXME: should be decidable
+          x_vect=plot.vectors[1][0]
+          
+          c=0
+          i=start_index
+          start=x_vect[start_index]
+          maxlen=len(x_vect)
+          while abs(x_vect[i]-x_vect[start_index])*(10**9) < nm:
+              if i==0 or i==maxlen-1: #we reached boundaries of vector!
+                  return c
+              
+              if backwards:
+                  i-=1
+              else:
+                  i+=1
+              c+=1
+          return c
+
+
+
+      def find_current_peaks(self,noflatten, a=True, maxpeak=True):
+            #Find peaks.
+            if a==True:
+                  a=self.convfilt_config['mindeviation']
+            try:
+                  abs_devs=float(a)
+            except:
+                  print "Bad input, using default."
+                  abs_devs=self.convfilt_config['mindeviation']
+
+            defplot=self.current.curve.default_plots()[0]
+            if not noflatten:
+                flatten=self._find_plotmanip('flatten') #Extract flatten plotmanip
+                defplot=flatten(defplot, self.current, customvalue=1) #Flatten curve before feeding it to has_peaks
+            pk_location,peak_size=self.has_peaks(defplot, abs_devs, maxpeak)
+            return pk_location, peak_size
+
+
+      def pickup_contact_point(self,N=1,whatset=1):
+            '''macro to pick up the contact point by clicking'''
+            contact_point=self._measure_N_points(N=1, whatset=1)[0]
+            contact_point_index=contact_point.index
+            self.wlccontact_point=contact_point
+            self.wlccontact_index=contact_point.index
+            self.wlccurrent=self.current.path
+            return contact_point, contact_point_index
+
+
+
+      def baseline_points(self,peak_location, displayed_plot):
+            clicks=self.config['baseline_clicks']
+            if clicks==0:
+                self.basepoints=[]
+                base_index_0=peak_location[-1]+self.fit_interval_nm(peak_location[-1], displayed_plot, self.config['auto_right_baseline'],False)
+                self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_0))
+                base_index_1=self.basepoints[0].index+self.fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'],False)
+                self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1))
+            elif clicks>0:
+                print 'Select baseline'
+                if clicks==1:
+                    self.basepoints=self._measure_N_points(N=1, whatset=1)
+                    base_index_1=self.basepoints[0].index+self.fit_interval_nm(self.basepoints[0].index, displayed_plot, self.config['auto_left_baseline'], False)
+                    self.basepoints.append(self._clickize(displayed_plot.vectors[1][0],displayed_plot.vectors[1][1],base_index_1))
+                else:
+                    self.basepoints=self._measure_N_points(N=2, whatset=1)
+            
+            self.basecurrent=self.current.path
+            return self.basepoints
index 80697aefffc9c81847eaa41af0b715dc44559611..371cd1e4573bad145535dae54ef1449193764b28 100644 (file)
@@ -16,8 +16,8 @@
 # License along with Hooke.  If not, see
 # <http://www.gnu.org/licenses/>.
 
-"""The `playlist` module provides :class:`PlaylistPlugin` several
-associated :class:`hooke.command.Command`\s for handling
+"""The ``playlist`` module provides :class:`PlaylistPlugin` and
+several associated :class:`hooke.command.Command`\s for handling
 :mod:`hooke.playlist` classes.
 """
 
@@ -53,8 +53,8 @@ def current_playlist_callback(hooke, command, argument, value):
 PlaylistArgument = Argument(
     name='playlist', type='playlist', callback=current_playlist_callback,
     help="""
-:class:`hooke.plugin.playlist.Playlist` to act on.  Defaults to the
-current playlist.
+:class:`hooke.playlist.Playlist` to act on.  Defaults to the current
+playlist.
 """.strip())
 
 def playlist_name_callback(hooke, command, argument, value):
diff --git a/test/curve_info.py b/test/curve_info.py
new file mode 100644 (file)
index 0000000..23bb4a2
--- /dev/null
@@ -0,0 +1,38 @@
+# Copyright (C) 2010 W. Trevor King <wking@drexel.edu>
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+"""
+>>> from hooke.hooke import Hooke, HookeRunner
+>>> h = Hooke()
+>>> r = HookeRunner()
+>>> h = r.run_lines(h, ['load_playlist test/data/test']) # doctest: +ELLIPSIS
+<FilePlaylist test.hkp>
+Success
+<BLANKLINE>
+>>> h = r.run_lines(h, ['curve_info'])
+name: picoforce.000
+path: test/data/picoforce.000
+experiment: None
+driver: picoforce
+filetype: None
+note: 
+blocks: 0
+block sizes: []
+Success
+<BLANKLINE>
+"""