From 933dd93987018f3842575092f069e991e6682764 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 16 May 2010 10:49:56 -0400 Subject: [PATCH] Add hooke.plugin.curve (moved from hooke.plugin.curvetools) 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 | 1 + hooke/plugin/curve.py | 149 ++++++++++++++++++++++++ hooke/plugin/curvetools.py | 210 ---------------------------------- hooke/plugin/generalvclamp.py | 77 +++++++++++++ hooke/plugin/playlist.py | 8 +- test/curve_info.py | 38 ++++++ 6 files changed, 269 insertions(+), 214 deletions(-) create mode 100644 hooke/plugin/curve.py delete mode 100644 hooke/plugin/curvetools.py create mode 100644 test/curve_info.py diff --git a/hooke/plugin/__init__.py b/hooke/plugin/__init__.py index 075cdff..89a23b2 100644 --- a/hooke/plugin/__init__.py +++ b/hooke/plugin/__init__.py @@ -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 index 0000000..1e0ad06 --- /dev/null +++ b/hooke/plugin/curve.py @@ -0,0 +1,149 @@ +# Copyright (C) 2010 Fibrin's Benedetti +# 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 +# . + +"""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 index 6fc2f3d..0000000 --- a/hooke/plugin/curvetools.py +++ /dev/null @@ -1,210 +0,0 @@ -# Copyright (C) 2010 Fabrizio Benedetti -# 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 ..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() diff --git a/hooke/plugin/generalvclamp.py b/hooke/plugin/generalvclamp.py index 9c0593e..a672143 100644 --- a/hooke/plugin/generalvclamp.py +++ b/hooke/plugin/generalvclamp.py @@ -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 diff --git a/hooke/plugin/playlist.py b/hooke/plugin/playlist.py index 80697ae..371cd1e 100644 --- a/hooke/plugin/playlist.py +++ b/hooke/plugin/playlist.py @@ -16,8 +16,8 @@ # License along with Hooke. If not, see # . -"""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 index 0000000..23bb4a2 --- /dev/null +++ b/test/curve_info.py @@ -0,0 +1,38 @@ +# 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() +>>> h = r.run_lines(h, ['load_playlist test/data/test']) # doctest: +ELLIPSIS + +Success + +>>> 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 + +""" -- 2.26.2