From 8d319b7731dbab6ae3e4e5efb0838ca206d54f1a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 17 May 2010 09:18:28 -0400 Subject: [PATCH] Started translating hooke.plugin.multifit to new Plugin/Command architecture. --- hooke/command.py | 1 + hooke/plugin/multifit.py | 584 ++++++++++++++++----------------------- 2 files changed, 246 insertions(+), 339 deletions(-) diff --git a/hooke/command.py b/hooke/command.py index fcea13e..c822cc5 100644 --- a/hooke/command.py +++ b/hooke/command.py @@ -100,6 +100,7 @@ class Command (object): """ def __init__(self, name, aliases=None, arguments=[], help=''): + # TODO: see_also=[other,command,instances,...] self.name = name if aliases == None: aliases = [] diff --git a/hooke/plugin/multifit.py b/hooke/plugin/multifit.py index 483bf37..986b781 100644 --- a/hooke/plugin/multifit.py +++ b/hooke/plugin/multifit.py @@ -17,379 +17,285 @@ # License along with Hooke. If not, see # . -#FIXME clean this, probably some dependencies are not needed +"""The ``multifit`` module provides :class:`MultifitPlugin` and +several associated :class:`hooke.command.Command`\s for fitting +all sorts of :class:`hooke.experiment.VelocityClamp` parameters. +""" -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 -import tempfile -import warnings -warnings.simplefilter('ignore',np.RankWarning) -import Queue +from ..command import Command, Argument, Failure +from ..playlist import FilePlaylist +from ..plugin import Plugin +from ..plugin.playlist import PlaylistArgument -global measure_wlc -global EVT_MEASURE_WLC -global events_from_fit -events_from_fit=Queue.Queue() #GUI ---> CLI COMMUNICATION +class MultifitPlugin (Plugin): + def __init__(self): + super(MultifitPlugin, self).__init__(name='multifit') + def commands(self): + return [MultifitCommand()] -class multifitCommands: +class MultifitCommand (Command): + """Presents curves for interactive manual analysis. - def do_multifit(self,args): - ''' - MULTIFIT - (multifit.py) - Presents curves for manual analysis in a comfortable mouse-only fashion. - Obtains contour length, persistance length, rupture force and - slope - loading rate. - WLC is shown in red, eFJC in blue. - ------------- - Syntax: - multifit [pl=value] [kl=value] [t=value] [slopew=value] [basew=value] - [slope2p] [justone] + Obtains contour length, persistance length, rupture force and + slope (loading rate). - pl=[value] and kl=[value]: Use a fixed persistent length (WLC) or Kuhn - length (eFJC) for the fit. If pl is not given, the fit will be - a 2-variable fit. - DO NOT put spaces between 'pl', '=' and the value. - eFJC fit works better with a fixed kl - The value must be in nanometers. + See :class:`hooke.plugin.fit.FitCommand` help for more information + on the options and fit procedures. + """ + #NOTE: centerzero plot modifier should be activated (set centerzero + #1). + def __init__(self): + super(MultifitCommand, self).__init__( + name='multifit', + arguments=[ + PlaylistArgument, + Argument(name='persistence length', type='float', + help=""" +Persistence length in meters for WLC fits. If not given, the fit will +be a 2-variable fit. +""".strip()), + Argument(name='Kuhn length', type='float', + help=""" +Kuhn length in meters for eFJC fits. If not given, the fit will be a +2-variable fit. The eFJC fit works better with a fixed Kuhn length +(citation?). +""".strip()), + Argument(name='temperature', type='float', default=293.0, + help=""" +Temperature in Kelvin. Defaults to 293 K. +""".strip()), + Argument(name='slope width', type='int', + help=""" +Width in points for slope fitting (points leading up to the rupture). +If not given, the routine will prompt for an extra point to fit the +slope. +""".strip()), + Argument(name='base width', type='int', default=15, + help=""" +Width in points for slope fitting (points following the rupture?). +""".strip()), + Argument(name='point slope', type='bool', default=False, + help=""" +Calculate the slope from the two selected points instead of fitting +data between them. +""".strip()), + Argument(name='just one', type='bool', default=False, + help=""" +Perform the fits on the current curve instead of iterating. +""".strip()), + ], + help=self.__doc__) - t=[value] : Use a user-defined temperature. The value must be in - kelvins; by default it is 293 K. - DO NOT put spaces between 't', '=' and the value. - - slopew and basew : width in points for slope fitting (points to the - right of clicked rupture) and base level fitting (points to - the left of clicked top of rupture). - If slopew is not provided, the routine will ask for a 5th - point to fit the slope. - DO NOT put spaces between 'slopew' or 'basew', '=' value. - - slope2p : calculates the slope from the two clicked points, - instead of fitting data between them - - justone : performs the fits over current curve instead of iterating - - See fit command help for more information on the options and fit - procedures. - NOTE: centerzero plot modifier should be activated (set centerzero 1). - ''' - - #NOTE duplicates a lot of code from do_fit in fit.py, a call to it could be used directly - #but it is easier to control the program flow bypassing it - - pl_value=None - kl_value=None - T=self.config['temperature'] - slopew=None - basew=15 - justone=False - twops=False - - #FIXME spaces are not allowed between pl or t and value - for arg in args.split(): - #look for a persistent length argument. - if 'pl=' in arg: - pl_expression=arg.split('=') - pl_value=float(pl_expression[1]) #actual value - if 'kl=' in arg: - kl_expression=arg.split('=') - kl_value=float(kl_expression[1]) #actual value - #look for a T argument. - if ('t=' in arg) or ('T=' in arg): - t_expression=arg.split('=') - T=float(t_expression[1]) - #look for a basew argument. - if ('basew=' in arg): - basew_expression=arg.split('=') - basew=int(basew_expression[1]) - #look for a slopew argument. - if ('slopew=' in arg): - slopew_expression=arg.split('=') - slopew=int(slopew_expression[1]) - if('justone' in arg): - justone=True - if('slope2p' in arg): - twops=True - + def _run(self, hooke, inqueue, outqueue, params): counter=0 savecounter=0 curveindex=0 - if not justone: - print 'What curve no. you would like to start? (enter for ignoring)' - skip=raw_input() - - if skip.isdigit()==False: - skip=0 - else: - skip=int(skip)-1 - print 'Skipping '+str(skip)+ ' curves' - else: - skip=0 - #begin presenting curves for analysis - while curveindex curveindex: - curveindex+=1 - continue - - #give periodically the opportunity to stop the analysis - if counter%20==0 and counter>0: - print '\nYou checked '+str(counter)+' curves. Do you want to continue?' - self.current=curve - self.do_plot(0) - if self.YNclick(): - print 'Going on...' - else: - break - else: - self.current=curve - self.do_plot(0) - else: - curve=self.current - self.do_plot(0) - if not justone: - print '\nCurve '+str(curveindex+1)+' of '+str(len(self.current_list)) - print 'Click contact point or left end (red area)of the curve to skip' - #hightlights an area dedicated to reject current curve - sizeret=len(self.plots[0].vectors[1][0]) - noarea=self.plots[0].vectors[1][0][-sizeret/6:-1] - noareaplot=copy.deepcopy(self._get_displayed_plot()) - #noise dependent slight shift to improve visibility - noareaplot.add_set(noarea,np.ones_like(noarea)*5.0*np.std(self.plots[0].vectors[1][1][-sizeret/6:-1])) - noareaplot.styles+=['scatter'] - noareaplot.colors+=["red"] - self._send_plot([noareaplot]) - contact_point=self._measure_N_points(N=1, whatset=1)[0] - contact_point_index=contact_point.index - noareaplot.remove_set(-1) - #remove set leaves some styles disturbing the next graph, fixed by doing do_plot - self.do_plot(0) - if contact_point_index>5.0/6.0*sizeret: - if justone: - break - curveindex+=1 - continue - - self.wlccontact_point=contact_point - self.wlccontact_index=contact_point.index - self.wlccurrent=self.current.path - - print 'Now click two points for the fitting area (one should be the rupture point)' - wlcpoints=self._measure_N_points(N=2,whatset=1) - print 'And one point of the top of the jump' - toppoint=self._measure_N_points(N=1,whatset=1) - slopepoint=ClickedPoint() - if slopew==None: - print 'Click a point to calculate slope' - slopepoint=self._measure_N_points(N=1,whatset=1) + start_curve = params['playlist'].current() + curve = start_curve + while True: + outqueue.put('TODO to end %s execution' % self.name) + self._fit_curve(hooke, inqueue, outqueue, params, curve) + if params['just one'] == True: + break + params['playlist'].next() + next_curve = params['playlist'].current() + if next_curve == start_curve: + break # loop through playlist complete + curve = next_curve - fitpoints=[contact_point]+wlcpoints - #use the currently displayed plot for the fit - displayed_plot=self._get_displayed_plot() - - #use both fit functions - try: - wlcparams, wlcyfit, wlcxfit, wlcfit_errors,wlc_qstd = self.wlc_fit(fitpoints, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1],pl_value,T, return_errors=True ) - wlcerror=False - except: - print 'WLC fit not possible' - wlcerror=True - - try: - fjcparams, fjcyfit, fjcxfit, fjcfit_errors,fjc_qstd = self.efjc_fit(fitpoints, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1],kl_value,T, return_errors=True ) - fjcerror=False - except: - print 'eFJC fit not possible' - fjcerror=True + def _fit_curve(self, hooke, inqueue, outqueue, params, curve): + contact = self._get_point('Mark the contact point of the curve.') + #hightlights an area dedicated to reject current curve + sizeret=len(self.plots[0].vectors[1][0]) + noarea=self.plots[0].vectors[1][0][-sizeret/6:-1] + noareaplot=copy.deepcopy(self._get_displayed_plot()) + #noise dependent slight shift to improve visibility + noareaplot.add_set(noarea,np.ones_like(noarea)*5.0*np.std(self.plots[0].vectors[1][1][-sizeret/6:-1])) + noareaplot.styles+=['scatter'] + noareaplot.colors+=["red"] + self._send_plot([noareaplot]) + contact_point=self._measure_N_points(N=1, whatset=1)[0] + contact_point_index=contact_point.index + noareaplot.remove_set(-1) + #remove set leaves some styles disturbing the next graph, fixed by doing do_plot + self.do_plot(0) + if contact_point_index>5.0/6.0*sizeret: + if params['just one']: + break + curveindex+=1 + continue + + self.wlccontact_point=contact_point + self.wlccontact_index=contact_point.index + self.wlccurrent=self.current.path + + print 'Now click two points for the fitting area (one should be the rupture point)' + wlcpoints=self._measure_N_points(N=2,whatset=1) + print 'And one point of the top of the jump' + toppoint=self._measure_N_points(N=1,whatset=1) + slopepoint=ClickedPoint() + if slopew==None: + print 'Click a point to calculate slope' + slopepoint=self._measure_N_points(N=1,whatset=1) + + fitpoints=[contact_point]+wlcpoints + #use the currently displayed plot for the fit + displayed_plot=self._get_displayed_plot() + + #use both fit functions + try: + wlcparams, wlcyfit, wlcxfit, wlcfit_errors,wlc_qstd = self.wlc_fit(fitpoints, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1],pl_value,T, return_errors=True ) + wlcerror=False + except: + print 'WLC fit not possible' + wlcerror=True + + try: + fjcparams, fjcyfit, fjcxfit, fjcfit_errors,fjc_qstd = self.efjc_fit(fitpoints, displayed_plot.vectors[1][0], displayed_plot.vectors[1][1],kl_value,T, return_errors=True ) + fjcerror=False + except: + print 'eFJC fit not possible' + fjcerror=True - #Measure rupture force - ruptpoint=ClickedPoint() - if wlcpoints[0].graph_coords[1]