From ee0cc702f9257cf5fdfb89fea2ebf70fbde3e54a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 4 May 2010 11:47:17 -0400 Subject: [PATCH] Really hideous merge of Rolf Schmidt's code. It's hard to merge code when you're not sure which version is more recent, what the rationale behind the changes are, or very much about how the code is supposed to work at all ;). I think I caught most of the important ideas from both sides, and now I'm going to go back through and clean things up. --- conf/autopeak.ini | 91 ++ conf/fit.ini | 3 + conf/flatfilts.ini | 84 ++ conf/general.ini | 22 + conf/generalvclamp.ini | 19 + conf/hooke configspec.ini | 64 + conf/hooke.ini | 121 ++ conf/pcluster.ini | 10 + conf/procplots.ini | 12 + doc/GUI | 28 + hooke/config.py | 155 ++ hooke/driver/mfp1dexport.py | 52 +- hooke/driver/picoforce.py | 21 +- hooke/formatter.py | 69 + hooke/hooke.py | 1978 +++++++++++++++---------- hooke/libhooke.py | 309 +--- hooke/libhookecurve.py | 86 +- hooke/playlist.py | 197 +++ hooke/plugin/__init__.py | 57 + hooke/plugin/autopeak.py | 10 +- hooke/plugin/flatfilts-rolf.py | 336 +++++ hooke/plugin/generalclamp.py | 2 +- hooke/plugin/generalvclamp.py | 6 + hooke/plugin/procplots.py | 2 +- hooke/plugin/showconvoluted.py | 61 + hooke/test/test.hkp | 6 +- hooke/ui/gui/hookeplaylist.py | 106 ++ hooke/ui/gui/hookepropertyeditor.py | 519 +++++++ hooke/ui/gui/hookeresults.py | 78 + hooke/ui/gui/perspectives/Default.txt | 1 + hooke/ui/gui/prettyformat.py | 141 ++ hooke/ui/gui/results.py | 150 ++ 32 files changed, 3664 insertions(+), 1132 deletions(-) create mode 100644 conf/autopeak.ini create mode 100644 conf/fit.ini create mode 100644 conf/flatfilts.ini create mode 100644 conf/general.ini create mode 100644 conf/generalvclamp.ini create mode 100644 conf/hooke configspec.ini create mode 100644 conf/hooke.ini create mode 100644 conf/pcluster.ini create mode 100644 conf/procplots.ini create mode 100644 doc/GUI create mode 100644 hooke/config.py create mode 100644 hooke/formatter.py create mode 100644 hooke/playlist.py create mode 100644 hooke/plugin/flatfilts-rolf.py create mode 100644 hooke/plugin/showconvoluted.py create mode 100644 hooke/ui/gui/hookeplaylist.py create mode 100644 hooke/ui/gui/hookepropertyeditor.py create mode 100644 hooke/ui/gui/hookeresults.py create mode 100644 hooke/ui/gui/perspectives/Default.txt create mode 100644 hooke/ui/gui/prettyformat.py create mode 100644 hooke/ui/gui/results.py diff --git a/conf/autopeak.ini b/conf/autopeak.ini new file mode 100644 index 0000000..3f6cf98 --- /dev/null +++ b/conf/autopeak.ini @@ -0,0 +1,91 @@ +[autopeak] + [[auto_fit_nm]] + default = 5 + minimum = 0 + type = float + value = 5 + + [[auto_fit_points]] + default = 50 + minimum = 0 + type = integer + value = 50 + + [[auto_left_baseline]] + default = 20 + minimum = 0 + type = float + value = 20 + + [[auto_max_p]] + default = 10 + minimum = 0 + type = float + value = 10 + + [[auto_min_p]] + default = 0.005 + minimum = 0 + type = float + value = 0.005 + + [[auto_right_baseline]] + default = 20 + minimum = 0 + type = float + value = 20 + + [[auto_slope_span]] + default = 20 + minimum = 0 + type = integer + value = 20 + + [[baseline_clicks]] + default = 0 + maximum = 20 + minimum = 0 + type = integer + value = 0 + + [[noauto]] + default = False + type = boolean + value = False + + [[noflatten]] + default = False + type = boolean + value = False + + [[pl]] + default = 5 + minimum = 0 + type = float + value = 5 + + [[rebase]] + default = True + type = boolean + value = False + + [[reclick]] + default = False + type = boolean + value = False + + [[temperature]] + default = 293 + minimum = 0 + type = float + value = 293 + + [[usepl]] + default = False + type = boolean + value = False + + [[usepoints]] + default = False + type = boolean + value = False diff --git a/conf/fit.ini b/conf/fit.ini new file mode 100644 index 0000000..d84e157 --- /dev/null +++ b/conf/fit.ini @@ -0,0 +1,3 @@ +[fit] +flatten = 1 +temperature = 301 \ No newline at end of file diff --git a/conf/flatfilts.ini b/conf/flatfilts.ini new file mode 100644 index 0000000..391b47a --- /dev/null +++ b/conf/flatfilts.ini @@ -0,0 +1,84 @@ +[convfilt] + [[blindwindow]] + default = 20 + maximum = 10000 + minimum = 0 + type = float + value = 20 + + [[convolution]] + default = '[6.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]' + type = string + #value = '[6.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]' + value = '[11.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]' + + [[maxcut]] + default = 0.2 + maximum = 1 + minimum = 0 + type = float + value = 0.2 + visible = False + + [[medianfilter]] + default = 7 + maximum = 100 + minimum = 0 + type = integer + value = 7 + + [[mindeviation]] + default = 5 + maximum = 100 + minimum = 0 + type = float + value = 5 + + [[minpeaks]] + default = 5 + maximum = 20 + minimum = 0 + type = integer + value = 1 + + [[positive]] + default = False + type = boolean + value = False + + [[seedouble]] + default = 10 + maximum = 1000 + minimum = 0 + type = integer + value = 10 + + [[stable]] + default = 0.005 + maximum = 1 + minimum = 0 + type = float + value = 0.005 + +[flatfilt] + #[[median_filter]] + #default = 7 + #maximum = 100 + #minimum = 0 + #type = integer + #value = 7 + + [[min_npks]] + default = 4 + maximum = 10000 + minimum = 1 + type = integer + value = 4 + + [[min_deviation]] + default = 9 + maximum = 100 + minimum = 1 + type = float + value = 9 + diff --git a/conf/general.ini b/conf/general.ini new file mode 100644 index 0000000..7b5e2cc --- /dev/null +++ b/conf/general.ini @@ -0,0 +1,22 @@ +[genlist] + [[folder]] + default = '' + type = folder + value = 'R:\Programming\Python\Gui\data\dwell' + + [[filemask]] + default = '*.*' + type = string + value = '*.*' + +[loadlist] + [[filename]] + default = 'test.hkp' + type = filename + value = 'R:\Programming\Python\Gui\untitled.hkp' + +[savelist] + [[filename]] + default = 'untitled.hkp' + type = filename + value = 'R:\Programming\Python\Gui\untitled.hkp' \ No newline at end of file diff --git a/conf/generalvclamp.ini b/conf/generalvclamp.ini new file mode 100644 index 0000000..45132fa --- /dev/null +++ b/conf/generalvclamp.ini @@ -0,0 +1,19 @@ +[generalvclamp] + [[flatten]] + default = True + type = boolean + value = True + + [[max_cycles]] + default = 1 + maximum = 100 + minimum = 0 + type = integer + value = 1 + + [[force_multiplier]] + default = 1 + maximum = 100 + minimum = 0 + type = float + value = 1 \ No newline at end of file diff --git a/conf/hooke configspec.ini b/conf/hooke configspec.ini new file mode 100644 index 0000000..b767181 --- /dev/null +++ b/conf/hooke configspec.ini @@ -0,0 +1,64 @@ +[drivers] +csvdriver = boolean(default = False) +hemingclamp = boolean(default = False) +jpk = boolean(default = False) +mcs = boolean(default = False) +mfp1dexport = boolean(default = True) +picoforce = boolean(default = True) +picoforcealt = boolean(default = False) +tutorialdriver = boolean(default = False) + +[folder] +filterindex = integer(default = 0) +filters = string(default = 'Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*') + +[general] +list = string(default = 'test.hkp') +workdir = string(default = '') + +[main] +height = integer(default = 500) +left = integer(default = 50) +top = integer(default = 50) +width = integer(default = 700) + +[plotmanipulators] +names(default = list(correct, median, absvalue, flatten, multiplier, clamp, threshold, coincident)) +absvalue = boolean(default = False) +clamp = boolean(default = True) +coincident = boolean(default = True) +correct = boolean(default = True) +flatten = boolean(default = True) +median = boolean(default = True) +multiplier = boolean(default = True) +threshold = boolean(default = True) + +[perspectives] +active = string(default = Default) + +[plugins] +autopeak = boolean(default = True) +dummyguiplug = boolean(default = False) +fit = boolean(default = True) +flatfilts = boolean(default = True) +generalclamp = boolean(default = True) +generaltccd = boolean(default = True) +generalvclamp = boolean(default = True) +macro = boolean(default = True) +massanalysis = boolean(default = True) +pcluster = boolean(default = True) +procplots = boolean(default = True) +superimpose = boolean(default = False) +tutorial = boolean(default = False) +viewer = boolean(default = True) + +[splashscreen] +#duration in milliseconds +duration = integer(default = 1000) +show = boolean(default = True) + +#name = string +#age = float +#attributes = string_list +#likes_cheese = boolean +#favourite_color = string \ No newline at end of file diff --git a/conf/hooke.ini b/conf/hooke.ini new file mode 100644 index 0000000..fb03cb1 --- /dev/null +++ b/conf/hooke.ini @@ -0,0 +1,121 @@ +#prefix with '#' to add a comment + +#Internal variables +[display] +colour_ext = None +colour_ret = None +ext = 1 +ret = 1 +correct = 1 +colour_correct = None +contact_point = 0 +medfilt = 0 +xaxes = 0 +yaxes = 0 +flatten = 1 +temperature = 301 +auto_fit_points = 50 +auto_slope_span = 20 +auto_delta_force = 10 +auto_fit_nm = 5 +auto_min_p = 0.005 +auto_max_p = 10 +baseline_clicks = 0 +auto_left_baseline = 20 +auto_right_baseline = 20 +force_multiplier = 1 +fc_showphase = 0 +fc_showimposed = 0 +fc_interesting = 0 +tccd_threshold = 0 +tccd_coincident = 0 + +[folders] +filterindex = 0 +filters = Playlist files (*.hkp)|*.hkp|Text files (*.txt)|*.txt|All files (*.*)|*.*') + +[general] +#substitute your work directory +#workdir = D:\hooke +workdir = R:\Programming\Python\Gui\ +#the default playlist to load at startup +list = test.hkp +#the default perspective to load at startup +perspective = Default +#splashscreen +splash = 1 +splashduration = 1000 + +#this section defines which plugins have to be loaded by Hooke +[plugins] +autopeak = True +dummyguiplug = False +fit = True +flatfilts = True +generalclamp = False +generaltccd = False +generalvclamp = True +macro = False +massanalysis = False +pcluster = False +procplots = True +showconvoluted = True +superimpose = False +tutorial = False +viewer = False +#autopeak = True +#dummyguiplug = False +#fit = True +#flatfilts = True +#generalclamp = True +#generaltccd = True +#generalvclamp = True +#macro = True +#massanalysis = True +#pcluster = True +#procplots = True +#superimpose = False +#tutorial = False +#viewer = True + +#this section defines which drivers have to be loaded by Hooke +[drivers] +csvdriver = False +hemingclamp = False +jpk = False +mcs = False +mfp1dexport = True +picoforce = True +picoforcealt = False +tutorialdriver = False + +##this section defines which plot manipulators have to be loaded by Hooke, +##and -IMPORTANTLY- their order. +[plotmanipulators] +absvalue = False +clamp = False +coincident = False +correct = True +flatten = False +median = False +multiplier = False +names = correct, median, absvalue, flatten, multiplier, clamp, threshold, coincident, showconvoluted +showconvoluted = False +threshold = False + +#this section defines the window size and position +[main] +height = 797 +left = 536 +top = 20 +width = 1092 + +[splashscreen] +#duration in milliseconds +duration = 1000 +show = False + +[perspectives] +active = Default + +[folder] diff --git a/conf/pcluster.ini b/conf/pcluster.ini new file mode 100644 index 0000000..1984027 --- /dev/null +++ b/conf/pcluster.ini @@ -0,0 +1,10 @@ +[pcluster] +auto_fit_nm = 5 +auto_fit_points = 50 +auto_left_baseline = 20 +auto_max_p = 10 +auto_min_p = 0.005 +auto_right_baseline = 20 +auto_slope_span = 20 +baseline_clicks = 0 +temperature = 301 \ No newline at end of file diff --git a/conf/procplots.ini b/conf/procplots.ini new file mode 100644 index 0000000..ab6d1a5 --- /dev/null +++ b/conf/procplots.ini @@ -0,0 +1,12 @@ +[procplots] + [[median]] + default = 0 + maximum = 100 + minimum = 0 + type = integer + value = 0 + + [[correct]] + default = True + type = boolean + value = True \ No newline at end of file diff --git a/doc/GUI b/doc/GUI new file mode 100644 index 0000000..a22c719 --- /dev/null +++ b/doc/GUI @@ -0,0 +1,28 @@ +# Interface +by Rolf Schmidt + +You can dock/undock all windows except for plot tab (I would like to +disable docking above the menubar and instead have the windows dock +beneath the menubar) + +You can save perspectives (delete perspectives however does not work yet). + +The 'Navigation' menubar works, the 'Main' menubar does not work yet. + +Closing plot tabs does not work properly, feedback on specific +examples and error messages would be much appreciated + +In the 'Folders' pane you can double-click playlists (with hkp +extension) to open them (you cannot double-click curves to open them) + +Commands in the 'Commands'-pane that work +- autopeak (only partially, everything that has to do with clicking on the plot does not work) +- genlist +- loadlist +- convfilt +- flatfilt + +When you open or generate a playlist, Hooke seems to hang. This is +actually not the case, just wait. Hooke loads all the curves into +memory and applies all the plotmanipulators. In the future, there will +be a progressbar to indicate the progress. diff --git a/hooke/config.py b/hooke/config.py new file mode 100644 index 0000000..75ea680 --- /dev/null +++ b/hooke/config.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +libhooke.py + +General library of internal objects and utilities for Hooke. + +Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy). +With algorithms contributed by Francesco Musiani (University of Bologna, Italy) + +This program is released under the GNU General Public License version 2. +''' + +import scipy +import numpy +import xml.dom.minidom +import os +import os.path +import string + +#import ConfigParser + +def config_file_path(filename, config_dir=None): + if config_dir == None: + config_dir = os.path.abspath( + os.path.join(os.path.dirname(os.path.dirname(__file__)), 'conf')) + return os.path.join(config_dir, filename) + +class HookeConfig(object): + ''' + Handling of Hooke configuration file + + Mostly based on the simple-yet-useful examples of the Python Library Reference + about xml.dom.minidom + + FIXME: starting to look a mess, should require refactoring + ''' + + def __init__(self, config_dir=None): + self.config={} + self.config['install']={} + self.config['plugins']=[] + self.config['drivers']=[] + self.config['plotmanips']=[] + self.config_dir = config_dir + + def load_config(self, filename): + myconfig=file(config_file_path(filename, config_dir=self.config_dir)) + + #the following 3 lines are needed to strip newlines. otherwise, since newlines + #are XML elements too, the parser would read them (and re-save them, multiplying + #newlines...) + #yes, I'm an XML n00b + the_file=myconfig.read() + the_file_lines=the_file.split('\n') + the_file=''.join(the_file_lines) + + self.config_tree=xml.dom.minidom.parseString(the_file) + + def getText(nodelist): + #take the text from a nodelist + #from Python Library Reference 13.7.2 + rc = '' + for node in nodelist: + if node.nodeType == node.TEXT_NODE: + rc += node.data + return rc + + def handleConfig(config): + install_elements=config.getElementsByTagName("install") + display_elements=config.getElementsByTagName("display") + plugins_elements=config.getElementsByTagName("plugins") + drivers_elements=config.getElementsByTagName("drivers") + defaultlist_elements=config.getElementsByTagName("defaultlist") + plotmanip_elements=config.getElementsByTagName("plotmanips") + handleInstall(install_elements) + handleDisplay(display_elements) + handlePlugins(plugins_elements) + handleDrivers(drivers_elements) + handleDefaultlist(defaultlist_elements) + handlePlotmanip(plotmanip_elements) + + def handleInstall(install_elements): + for install in install_elements: + for node in install.childNodes: + if node.nodeType == node.TEXT_NODE: + continue + path = os.path.abspath(getText(node.childNodes).strip()) + self.config['install'][str(node.tagName)] = path + + def handleDisplay(display_elements): + for element in display_elements: + for attribute in element.attributes.keys(): + self.config[attribute]=element.getAttribute(attribute) + + def handlePlugins(plugins): + for plugin in plugins[0].childNodes: + try: + self.config['plugins'].append(str(plugin.tagName)) + except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... + pass + #FIXME: code duplication + def handleDrivers(drivers): + for driver in drivers[0].childNodes: + try: + self.config['drivers'].append(str(driver.tagName)) + except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... + pass + + def handlePlotmanip(plotmanips): + for plotmanip in plotmanips[0].childNodes: + try: + self.config['plotmanips'].append(str(plotmanip.tagName)) + except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... + pass + + def handleDefaultlist(defaultlist): + ''' + default playlist + ''' + dflist=getText(defaultlist[0].childNodes) + self.config['defaultlist']=dflist.strip() + + handleConfig(self.config_tree) + #making items in the dictionary more machine-readable + for item in self.config.keys(): + try: + self.config[item]=float(self.config[item]) + except TypeError: #we are dealing with a list, probably. keep it this way. + try: + self.config[item]=eval(self.config[item]) + except: #not a list, not a tuple, probably a string? + pass + except ValueError: #if we can't get it to a number, it must be None or a string + if string.lower(self.config[item])=='none': + self.config[item]=None + else: + pass + + return self.config + + + def save_config(self, config_filename): + print 'Not Implemented.' + pass + + +def debug(): + ''' + debug stuff from latest rewrite of hooke_playlist.py + should be removed sooner or later (or substituted with new debug code!) + ''' + confo=HookeConfig() + print confo.load_config('hooke.conf') diff --git a/hooke/driver/mfp1dexport.py b/hooke/driver/mfp1dexport.py index 37af7df..3f1dbab 100644 --- a/hooke/driver/mfp1dexport.py +++ b/hooke/driver/mfp1dexport.py @@ -6,16 +6,25 @@ Driver for text-exported MFP 1D files Massimo Sandal (c) 2009 ''' -from .. import libhookecurve as lhc +import os + from .. import libhooke as lh +from .. import libhookecurve as lhc + + +__version__='0.0.0.20090923' + class mfp1dexportDriver(lhc.Driver): def __init__(self, filename): - - self.filename=filename - self.filedata=open(filename,'rU') - self.lines=list(self.filedata.readlines()) + ''' + This is a driver to import Asylum Research MFP 1D data. + Status: experimental + ''' + self.filename = filename + self.filedata = open(filename,'rU') + self.lines = list(self.filedata.readlines()) self.filedata.close() self.filetype='mfp1dexport' @@ -26,13 +35,13 @@ class mfp1dexportDriver(lhc.Driver): def is_me(self): try: - self.raw_header=self.lines[0:38] + self.raw_header = self.lines[0:38] except: #Not enough lines for a header; not a good file return False #FIXME: We want a more reasonable header recognition - if self.raw_header[0][0:4]=='Wave': + if self.raw_header[0].startswith('Wave'): return True else: return False @@ -52,7 +61,6 @@ class mfp1dexportDriver(lhc.Driver): #self.k=float(self.raw_header[23][8:]) self.k=float(kline[1]) - xext=[] xret=[] yext=[] @@ -67,20 +75,22 @@ class mfp1dexportDriver(lhc.Driver): return [[xext,yext],[xret,yret]] def deflection(self): - self.data=self._read_columns() - return self.data[0][1],self.data[1][1] - + self.data = self._read_columns() + return self.data[0][1], self.data[1][1] def default_plots(self): - main_plot=lhc.PlotObject() - defl_ext,defl_ret=self.deflection() - yextforce=[i*self.k for i in defl_ext] - yretforce=[i*self.k for i in defl_ret] - main_plot.add_set(self.data[0][0],yextforce) - main_plot.add_set(self.data[1][0],yretforce) + main_plot = lhc.PlotObject() + defl_ext,defl_ret = self.deflection() + yextforce = [i*self.k for i in defl_ext] + yretforce = [i*self.k for i in defl_ret] + main_plot.add_set(self.data[0][0], yextforce) + main_plot.add_set(self.data[1][0], yretforce) main_plot.normalize_vectors() - main_plot.units=['Z','force'] #FIXME: if there's an header saying something about the time count, should be used - main_plot.destination=0 - main_plot.title=self.filename - #main_plot.colors=['red','blue'] + #main_plot.units = ['Z','force'] #FIXME: if there's an header saying something about the time count, should be used + main_plot.units = ['m','N'] + main_plot.destination = 0 + main_plot.filename = self.filename + main_plot.title = os.path.basename(self.filename) + main_plot.colors = ['red','blue'] + main_plot.style = ['plot','plot'] return [main_plot] diff --git a/hooke/driver/picoforce.py b/hooke/driver/picoforce.py index c60e57c..51e9d21 100644 --- a/hooke/driver/picoforce.py +++ b/hooke/driver/picoforce.py @@ -8,12 +8,15 @@ Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy). This program is released under the GNU General Public License version 2. ''' -import re, struct +import re +import struct from scipy import arange +#from .. import libhooke as lh from .. import libhookecurve as lhc -__version__='0.0.0.20080404' + +__version__='0.0.0.20090923' class DataChunk(list): @@ -35,7 +38,7 @@ class picoforceDriver(lhc.Driver): ''' constructor method ''' - + filename = lh.get_file_path(filename) self.textfile=file(filename) self.binfile=file(filename,'rb') @@ -533,11 +536,13 @@ class picoforceDriver(lhc.Driver): main_plot=lhc.PlotObject() - main_plot.vectors=[[zdomain.ext()[0:samples], force.ext()[0:samples]],[zdomain.ret()[0:samples], force.ret()[0:samples]]] + main_plot.vectors = [[zdomain.ext()[0:samples], force.ext()[0:samples]],[zdomain.ret()[0:samples], force.ret()[0:samples]]] main_plot.normalize_vectors() - main_plot.units=['meters','newton'] - main_plot.destination=0 - main_plot.title=self.filepath - + main_plot.units = ['meters','newton'] + main_plot.destination = 0 + main_plit.filename = self.filepath + main_plot.title = self.filepath + main_plot.colors = ['red', 'blue'] + main_plit.styles = ['plot', 'plot'] return [main_plot] diff --git a/hooke/formatter.py b/hooke/formatter.py new file mode 100644 index 0000000..c5868e9 --- /dev/null +++ b/hooke/formatter.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +formatter.py + +Number formatting utilities for Hooke. + +Copyright (C) 2006 Massimo Sandal (University of Bologna, Italy). +With algorithms contributed by Francesco Musiani (University of Bologna, Italy) + +This program is released under the GNU General Public License version 2. +''' + +from matplotlib.ticker import ScalarFormatter + +from . import libhookecurve as lhc + + +class EngrFormatter(ScalarFormatter): + """A variation of the standard ScalarFormatter, using only multiples of +three +in the mantissa. A fixed number of decimals can be displayed with the optional +parameter `ndec` . If `ndec` is None (default), the number of decimals is +defined +from the current ticks. + """ + def __init__(self, ndec=None, useOffset=True, useMathText=False): + ScalarFormatter.__init__(self, useOffset, useMathText) + if ndec is None or ndec < 0: + self.format = None + elif ndec == 0: + self.format = "%d" + else: + self.format = "%%1.%if" % ndec + + def _set_orderOfMagnitude(self, mrange): + """Sets the order of magnitude.""" + locs = numpy.absolute(self.locs) + if self.offset: + oom = numpy.floor(numpy.log10(mrange)) + else: + if locs[0] > locs[-1]: + val = locs[0] + else: + val = locs[-1] + if val == 0: + oom = 0 + else: + oom = numpy.floor(numpy.log10(val)) + if oom <= -3: + self.orderOfMagnitude = 3*(oom//3) + elif oom <= -1: + self.orderOfMagnitude = -3 + elif oom >= 4: + self.orderOfMagnitude = 3*(oom//3) + else: + self.orderOfMagnitude = 0 + + def _set_format(self): + """Sets the format string to format all ticklabels.""" + # set the format string to format all the ticklabels + locs = (numpy.array(self.locs)-self.offset) / 10**self.orderOfMagnitude+1e-15 + sigfigs = [len(str('%1.3f'% loc).split('.')[1].rstrip('0')) \ + for loc in locs] + sigfigs.sort() + if self.format is None: + self.format = '%1.' + str(sigfigs[-1]) + 'f' + if self._usetex or self._useMathText: self.format = '$%s$'%self.format diff --git a/hooke/hooke.py b/hooke/hooke.py index 5fbc40b..3662b51 100644 --- a/hooke/hooke.py +++ b/hooke/hooke.py @@ -1,830 +1,1262 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- ''' HOOKE - A force spectroscopy review & analysis tool -(C) 2008 Massimo Sandal - -Copyright (C) 2008 Massimo Sandal (University of Bologna, Italy). +Copyright (C) 2008-2010 Massimo Sandal (University of Bologna, Italy). + Rolf Schmidt (Concordia University, Canada). This program is released under the GNU General Public License version 2. ''' -from .libhooke import HOOKE_VERSION, WX_GOOD - -import os +from libhooke import WX_GOOD import wxversion wxversion.select(WX_GOOD) +import copy +import cStringIO +import os +import os.path +import sys +import glob +import time + +import imp import wx +import wx.html +import wx.aui import wxmpl -from wx.lib.newevent import NewEvent - -import matplotlib.numerix as nx -import scipy as sp - -from threading import Thread -import Queue - -from .hooke_cli import HookeCli -from .libhooke import HookeConfig, ClickedPoint -from . import libhookecurve as lhc - -#import file versions, just to know with what we're working... -from hooke_cli import __version__ as hookecli_version +import wx.lib.agw.aui as aui +import wx.propgrid as wxpg + +import libhooke as lh +from config import config +import drivers +import plugins +import hookecommands +import hookeplaylist +import hookepropertyeditor +import hookeresults +import playlist global __version__ -global events_from_gui -global config -global CLI_PLUGINS -global GUI_PLUGINS -global LOADED_PLUGINS -global PLOTMANIP_PLUGINS -global FILE_DRIVERS - -__version__=HOOKE_VERSION[0] -__release_name__=HOOKE_VERSION[1] - -events_from_gui=Queue.Queue() #GUI ---> CLI COMMUNICATION - -print 'Starting Hooke.' -#CONFIGURATION FILE PARSING -config_obj=HookeConfig() -config=config_obj.load_config('hooke.conf') - -#IMPORTING PLUGINS - -CLI_PLUGINS=[] -GUI_PLUGINS=[] -PLOTMANIP_PLUGINS=[] -LOADED_PLUGINS=[] - -plugin_commands_namespaces=[] -plugin_gui_namespaces=[] -for plugin_name in config['plugins']: - try: - hooke_module=__import__('hooke.plugin.'+plugin_name) - plugin = getattr(hooke_module.plugin, plugin_name) - try: - #take Command plugin classes - commands = getattr(plugin, plugin_name+'Commands') - CLI_PLUGINS.append(commands) - plugin_commands_namespaces.append(dir(commands)) - except: - pass - try: - #take Gui plugin classes - gui = getattr(plugin, plugin_name+'Gui') - GUI_PLUGINS.append(gui) - plugin_gui_namespaces.append(dir(gui)) - except: - pass - except ImportError: - print 'Cannot find plugin ',plugin_name - else: - LOADED_PLUGINS.append(plugin_name) - print 'Imported plugin ',plugin_name - -#eliminate names common to all namespaces -for i,namespace in enumerate(plugin_commands_namespaces): - plugin_commands_namespaces[i] = \ - filter(lambda item : not (item.startswith('__') - or item == '_plug_init'), - namespace) -#check for conflicts in namespaces between plugins -#FIXME: only in commands now, because I don't have Gui plugins to check -#FIXME: how to check for plugin-defined variables (self.stuff) ?? -plugin_commands_names=[] -whatplugin_defines=[] -plugin_gui_names=[] -for namespace,plugin_name in zip(plugin_commands_namespaces, config['plugins']): - for item in namespace: - if item in plugin_commands_names: - i=plugin_commands_names.index(item) #we exploit the fact index gives the *first* occurrence of a name... - print 'Error. Plugin %s defines a function %s already defined by %s!' \ - % (plugin_name, item, whatplugin_defines[i]) - print 'This should not happen. Please disable one or both plugins and contact the plugin authors to solve the conflict.' - print 'Hooke cannot continue.' - exit() - else: - plugin_commands_names.append(item) - whatplugin_defines.append(plugin_name) - - -config['loaded_plugins']=LOADED_PLUGINS #FIXME: kludge -this should be global but not in config! -#IMPORTING DRIVERS -#FIXME: code duplication -FILE_DRIVERS=[] -LOADED_DRIVERS=[] -for driver_name in config['drivers']: - try: - hooke_module=__import__('hooke.driver.'+driver_name) - driver = getattr(hooke_module.driver, driver_name) - try: - FILE_DRIVERS.append(getattr(driver, driver_name+'Driver')) - except: - pass - except ImportError: - print 'Cannot find driver ',driver_name - else: - LOADED_DRIVERS.append(driver_name) - print 'Imported driver ',driver_name -config['loaded_drivers']=LOADED_DRIVERS - -#LIST OF CUSTOM WX EVENTS FOR CLI ---> GUI COMMUNICATION -#FIXME: do they need to be here? -list_of_events={} - -plot_graph, EVT_PLOT = NewEvent() -list_of_events['plot_graph']=plot_graph - -plot_contact, EVT_PLOT_CONTACT = NewEvent() -list_of_events['plot_contact']=plot_contact - -measure_points, EVT_MEASURE_POINTS = NewEvent() -list_of_events['measure_points']=measure_points - -export_image, EVT_EXPORT_IMAGE = NewEvent() -list_of_events['export_image']=export_image - -close_plot, EVT_CLOSE_PLOT = NewEvent() -list_of_events['close_plot'] = close_plot - -show_plots, EVT_SHOW_PLOTS = NewEvent() -list_of_events['show_plots'] = show_plots - -get_displayed_plot, EVT_GET_DISPLAYED_PLOT = NewEvent() -list_of_events['get_displayed_plot'] = get_displayed_plot -#------------ - -class CliThread(Thread): - - def __init__(self,frame,list_of_events): - Thread.__init__(self) - - #here we have to put temporary references to pass to the cli object. - self.frame=frame - self.list_of_events=list_of_events - - self.debug=0 #to be used in the future - - def run(self): - print '\n\nThis is Hooke, version',__version__ , __release_name__ - print - print '(c) Massimo Sandal & others, 2006-2008. Released under the GNU Lesser General Public License Version 3' - print 'Hooke is Free software.' - print '----' - print '' - - def make_command_class(*bases): - #FIXME: perhaps redundant - return type(HookeCli)("HookeCliPlugged", bases + (HookeCli,), {}) - cli = make_command_class(*CLI_PLUGINS)(self.frame,self.list_of_events,events_from_gui,config,FILE_DRIVERS) - cli.cmdloop() -''' -GUI CODE +__version__ = lh.HOOKE_VERSION[0] +__release_name__ = lh.HOOKE_VERSION[1] + +#TODO: order menu items, get rid of all unused IDs +ID_ExportText = wx.NewId() +ID_ExportImage = wx.NewId() +ID_Config = wx.NewId() +ID_About = wx.NewId() +ID_Next = wx.NewId() +ID_Previous = wx.NewId() + +ID_ViewAssistant = wx.NewId() +ID_ViewCommands = wx.NewId() +ID_ViewFolders = wx.NewId() +ID_ViewOutput = wx.NewId() +ID_ViewPlaylists = wx.NewId() +ID_ViewProperties = wx.NewId() +ID_ViewResults = wx.NewId() + +ID_CommandsList = wx.NewId() +ID_CommandsListBox = wx.NewId() + +ID_TextContent = wx.NewId() +ID_TreeContent = wx.NewId() +ID_HTMLContent = wx.NewId() +ID_SizeReportContent = wx.NewId() +ID_DeletePerspective = wx.NewId() +ID_SavePerspective = wx.NewId() + +ID_FirstPerspective = ID_SavePerspective + 1000 +#I hope we'll never have more than 1000 perspectives +ID_FirstPlot = ID_SavePerspective + 2000 + +class Hooke(wx.App): + + def OnInit(self): + self.SetAppName('Hooke') + self.SetVendorName('') + + #set the Hooke directory + lh.hookeDir = os.path.abspath(os.path.dirname(__file__)) + + windowPosition = (config['main']['left'], config['main']['top']) + windowSize = (config['main']['width'], config['main']['height']) + + #setup the splashscreen + if config['splashscreen']['show']: + filename = lh.get_file_path('hooke.jpg', ['resources']) + if os.path.isfile(filename): + bitmap = wx.Image(filename).ConvertToBitmap() + splashStyle = wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT + splashDuration = config['splashscreen']['duration'] + wx.SplashScreen(bitmap, splashStyle, splashDuration, None, -1) + wx.Yield() + ''' + we need for the splash screen to disappear + for whatever reason splashDuration and sleep do not correspond to each other + at least not on Windows + maybe it's because duration is in milliseconds and sleep in seconds + thus we need to increase the sleep time a bit + a factor of 1.2 seems to work quite well + ''' + sleepFactor = 1.2 + time.sleep(sleepFactor * splashDuration / 1000) + + plugin_objects = [] + for plugin in config['plugins']: + if config['plugins'][plugin]: + filename = ''.join([plugin, '.py']) + path = lh.get_file_path(filename, ['plugins']) + if os.path.isfile(path): + #get the corresponding filename and path + plugin_name = ''.join(['plugins.', plugin]) + module = __import__(plugin_name) + #get the file that contains the plugin + class_file = getattr(plugins, plugin) + #get the class that contains the commands + class_object = getattr(class_file, plugin + 'Commands') + plugin_objects.append(class_object) -FIXME: put it in a separate module in the future? -''' -class MainMenuBar(wx.MenuBar): - ''' - Creates the menu bar - ''' - def __init__(self): - wx.MenuBar.__init__(self) - '''the menu description. the key of the menu is XX&Menu, where XX is a number telling - the order of the menus on the menubar. - &Menu is the Menu text - the corresponding argument is ('&Item', 'itemname'), where &Item is the item text and itemname - the inner reference to use in the self.menu_items dictionary. - - See create_menus() to see how it works - - Note: the mechanism on page 124 of "wxPython in Action" is less awkward, maybe, but I want - binding to be performed later. Perhaps I'm wrong :) - ''' - - self.menu_desc={'00&File':[('&Open playlist','openplaymenu'),('&Exit','exitmenu')], - '01&Edit':[('&Export text...','exporttextmenu'),('&Export image...','exportimagemenu')], - '02&Help':[('&About Hooke','aboutmenu')]} - self.create_menus() - - def create_menus(self): + def make_command_class(*bases): + #create metaclass with plugins and plotmanipulators + return type(HookeFrame)("HookeFramePlugged", bases + (HookeFrame,), {}) + frame = make_command_class(*plugin_objects)(parent=None, id=wx.ID_ANY, title='Hooke', pos=windowPosition, size=windowSize) + frame.Show(True) + self.SetTopWindow(frame) + + return True + + def OnExit(self): + #TODO: write values to ini file if necessary + return True + + +class HookeFrame(wx.Frame): + + def __init__(self, parent, id=-1, title='', pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN): + #call parent constructor + wx.Frame.__init__(self, parent, id, title, pos, size, style) + self.config = config + self.CreateApplicationIcon() + #self.configs contains: {the name of the Commands file: corresponding ConfigObj} + self.configs = {} + ##self.playlists contains: {the name of the playlist: [playlist, tabIndex, plotID]} + #self.playlists = {} + #self.plugins contains: {the name of the plugin: [caption, function]} + self.plugins = {} + #self.plotmanipulators list contains: [the name of the plotmanip, function, name of the module] + self.plotmanipulators = [] + + #tell FrameManager to manage this frame + self._mgr = aui.AuiManager() + self._mgr.SetManagedWindow(self) + #set the gradient style + self._mgr.GetArtProvider().SetMetric(aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE) + #set transparent drag + self._mgr.SetFlags(self._mgr.GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG) + + # set up default notebook style + self._notebook_style = aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER + self._notebook_theme = 0 + + #holds the perspectives: {name, [index, perspectiveStr]} + self._perspectives = {} + + # min size for the frame itself isn't completely done. + # see the end up FrameManager::Update() for the test + # code. For now, just hard code a frame minimum size + self.SetMinSize(wx.Size(400, 300)) + #create panels here + self.panelAssistant = self.CreatePanelAssistant() + self.panelCommands = self.CreatePanelCommands() + self.panelFolders = self.CreatePanelFolders() + self.panelPlaylists = self.CreatePanelPlaylists() + self.panelProperties = self.CreatePanelProperties() + self.panelOutput = self.CreatePanelOutput() + self.panelResults = self.CreatePanelResults() + self.plotNotebook = self.CreateNotebook() + #self.textCtrlCommandLine=self.CreateCommandLine() + + # add panes + self._mgr.AddPane(self.panelFolders, aui.AuiPaneInfo().Name('Folders').Caption('Folders').Left().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.panelPlaylists, aui.AuiPaneInfo().Name('Playlists').Caption('Playlists').Left().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.plotNotebook, aui.AuiPaneInfo().Name('Plots').CenterPane().PaneBorder(False)) + self._mgr.AddPane(self.panelCommands, aui.AuiPaneInfo().Name('Commands').Caption('Settings and commands').Right().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.panelProperties, aui.AuiPaneInfo().Name('Properties').Caption('Properties').Right().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.panelAssistant, aui.AuiPaneInfo().Name('Assistant').Caption('Assistant').Right().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.panelOutput, aui.AuiPaneInfo().Name('Output').Caption('Output').Bottom().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.panelResults, aui.AuiPaneInfo().Name('Results').Caption('Results').Bottom().CloseButton(True).MaximizeButton(False)) + #self._mgr.AddPane(self.textCtrlCommandLine, aui.AuiPaneInfo().Name('CommandLine').CaptionVisible(False).Fixed().Bottom().Layer(2).CloseButton(False).MaximizeButton(False)) + #self._mgr.AddPane(panelBottom, aui.AuiPaneInfo().Name("panelCommandLine").Bottom().Position(1).CloseButton(False).MaximizeButton(False)) + + # add the toolbars to the manager + self.toolbar=self.CreateToolBar() + self.toolbarNavigation=self.CreateToolBarNavigation() + self._mgr.AddPane(self.toolbar, aui.AuiPaneInfo().Name('toolbar').Caption('Toolbar').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False)) + self._mgr.AddPane(self.toolbarNavigation, aui.AuiPaneInfo().Name('toolbarNavigation').Caption('Navigation').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False)) + # "commit" all changes made to FrameManager + self._mgr.Update() + #create the menubar after the panes so that the default perspective + #is created with all panes open + self.CreateMenuBar() + self.statusbar = self.CreateStatusBar() + self._BindEvents() + #TODO: select item on startup (whatever item) + #self.listCtrlCommands.Select(0) + #self.OnListboxSelect(None) + name = self.config['perspectives']['active'] + menu_item = self.GetPerspectiveMenuItem(name) + self.OnRestorePerspective(menu_item) + self.playlists = self.panelPlaylists.Playlists + #define the list of active drivers + self.drivers = [] + for driver in self.config['drivers']: + if self.config['drivers'][driver]: + #get the corresponding filename and path + filename = ''.join([driver, '.py']) + path = lh.get_file_path(filename, ['drivers']) + #the driver is active for driver[1] == 1 + if os.path.isfile(path): + #driver files are located in the 'drivers' subfolder + driver_name = ''.join(['drivers.', driver]) + module = __import__(driver_name) + class_file = getattr(drivers, driver) + for command in dir(class_file): + if command.endswith('Driver'): + self.drivers.append(getattr(class_file, command)) + #import all active plugins and plotmanips + #the plotmanip_functions contains: {the name of the plotmanip: [method, class_object]} + plotmanip_functions = {} + #add 'general.ini' to self.configs (this is not a plugin and thus must be imported seperately) + ini_path = lh.get_file_path('general.ini', ['plugins']) + plugin_config = ConfigObj(ini_path) + #self.config.merge(plugin_config) + self.configs['general'] = plugin_config + #make sure we execute _plug_init() for every command line plugin we import + for plugin in self.config['plugins']: + if self.config['plugins'][plugin]: + filename = ''.join([plugin, '.py']) + path = lh.get_file_path(filename, ['plugins']) + if os.path.isfile(path): + #get the corresponding filename and path + plugin_name = ''.join(['plugins.', plugin]) + try: + #import the module + module = __import__(plugin_name) + #prepare the ini file for inclusion + ini_path = path.replace('.py', '.ini') + #include ini file + plugin_config = ConfigObj(ini_path) + #self.config.merge(plugin_config) + self.configs[plugin] = plugin_config + #add to plugins + commands = eval('dir(module.' + plugin+ '.' + plugin + 'Commands)') + #keep only commands (ie names that start with 'do_') + commands = [command for command in commands if command.startswith('do_')] + if commands: + self.plugins[plugin] = commands + try: + #initialize the plugin + eval('module.' + plugin+ '.' + plugin + 'Commands._plug_init(self)') + except AttributeError: + pass + except ImportError: + pass + #initialize the commands tree + commands = dir(HookeFrame) + commands = [command for command in commands if command.startswith('do_')] + if commands: + self.plugins['general'] = commands + self.panelCommands.Initialize(self.plugins) + for command in dir(self): + if command.startswith('plotmanip_'): + plotmanip_functions[command] = [command, getattr(self, command)] + for name in self.config['plotmanipulators']['names']: + if self.config['plotmanipulators'].as_bool(name): + command_name = ''.join(['plotmanip_', name]) + if command_name in plotmanip_functions: + self.plotmanipulators.append(plotmanip_functions[command_name]) + #load default list, if possible + self.do_loadlist(self.config['general']['list']) + + def _BindEvents(self): + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_CLOSE, self.OnClose) + # Show How To Use The Closing Panes Event + self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose) + self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnNotebookPageClose) + #menu + self.Bind(wx.EVT_MENU, self.OnClose, id=wx.ID_EXIT) + self.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT) + #view + self.Bind(wx.EVT_MENU_RANGE, self.OnView, id=ID_ViewAssistant, id2=ID_ViewResults) + #perspectives + self.Bind(wx.EVT_MENU, self.OnDeletePerspective, id=ID_DeletePerspective) + self.Bind(wx.EVT_MENU, self.OnSavePerspective, id=ID_SavePerspective) + self.Bind(wx.EVT_MENU_RANGE, self.OnRestorePerspective, id=ID_FirstPerspective, id2=ID_FirstPerspective+1000) + #toolbar + self.Bind(wx.EVT_TOOL, self.OnExportImage, id=ID_ExportImage) + self.Bind(wx.EVT_TOOL, self.OnNext, id=ID_Next) + self.Bind(wx.EVT_TOOL, self.OnPrevious, id=ID_Previous) + #self.Bind(.EVT_AUITOOLBAR_TOOL_DROPDOWN, self.OnDropDownToolbarItem, id=ID_DropDownToolbarItem) + #dir control + treeCtrl = self.panelFolders.GetTreeCtrl() + #tree.Bind(wx.EVT_LEFT_UP, self.OnDirCtrl1LeftUp) + #tree.Bind(wx.EVT_LEFT_DOWN, self.OnGenericDirCtrl1LeftDown) + treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self.OnDirCtrlLeftDclick) + #playlist tree + self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DOWN, self.OnPlaylistsLeftDown) + self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DCLICK, self.OnPlaylistsLeftDclick) + #commands tree + self.panelCommands.ExecuteButton.Bind(wx.EVT_BUTTON, self.OnExecute) + self.panelCommands.CommandsTree.Bind(wx.EVT_LEFT_DOWN, self.OnTreeCtrlCommandsLeftDown) + #property editor + self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged) + self.panelProperties.pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect) + #results panel + self.panelResults.results_list.OnCheckItem = self.OnResultsCheck + + def _GetActiveCurveIndex(self): + playlist = self.GetActivePlaylist() + #get the selected item from the tree + selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() + #test if a playlist or a curve was double-clicked + if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): + return -1 + else: + count = 0 + selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item) + while selected_item.IsOk(): + count += 1 + selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item) + return count + + def _GetActivePlaylistName(self): + #get the selected item from the tree + selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() + #test if a playlist or a curve was double-clicked + if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): + playlist_item = selected_item + else: + #get the name of the playlist + playlist_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item) + #now we have a playlist + return self.panelPlaylists.PlaylistsTree.GetItemText(playlist_item) + + def _GetPlaylistTab(self, name): + for index, page in enumerate(self.plotNotebook._tabs._pages): + if page.caption == name: + return index + return -1 + + def _GetUniquePlaylistName(self, name): + playlist_name = name + count = 1 + while playlist_name in self.playlists: + playlist_name = ''.join([name, str(count)]) + count += 1 + return playlist_name + + def _SavePerspectiveToFile(self, name, perspective): + filename = ''.join([name, '.txt']) + filename = lh.get_file_path(filename, ['perspectives']) + perspectivesFile = open(filename, 'w') + perspectivesFile.write(perspective) + perspectivesFile.close() + + def AddPlaylist(self, playlist=None, name='Untitled'): + #TODO: change cursor or progressbar (maybe in statusbar) + #self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + if playlist and playlist.count > 0: + playlist.name = self._GetUniquePlaylistName(name) + playlist.reset() + self.AddToPlaylists(playlist) + + def AddPlaylistFromFiles(self, files=[], name='Untitled'): + #TODO: change cursor or progressbar (maybe in statusbar) + #self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + if files: + playlist = Playlist.Playlist(self.drivers) + for item in files: + playlist.add_curve(item) + if playlist.count > 0: + playlist.name = self._GetUniquePlaylistName(name) + playlist.reset() + self.AddToPlaylists(playlist) + + def AddToPlaylists(self, playlist): + if playlist.has_curves: + #setup the playlist in the Playlist tree + tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem() + playlist_root = self.panelPlaylists.PlaylistsTree.AppendItem(tree_root, playlist.name, 0) + #add all curves to the Playlist tree + curves = {} + for index, curve in enumerate(playlist.curves): + ##remove the extension from the name of the curve + ##TODO: optional? + #item_text, extension = os.path.splitext(curve.name) + #curve_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, item_text, 1) + curve_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, curve.name, 1) + if index == playlist.index: + self.panelPlaylists.PlaylistsTree.SelectItem(curve_ID) + playlist.reset() + #create the plot tab and add playlist to the dictionary + plotPanel = wxmpl.PlotPanel(self, ID_FirstPlot + len(self.playlists)) + notebook_tab = self.plotNotebook.AddPage(plotPanel, playlist.name, True) + tab_index = self.plotNotebook.GetSelection() + figure = plotPanel.get_figure() + self.playlists[playlist.name] = [playlist, figure] + self.panelPlaylists.PlaylistsTree.Expand(playlist_root) + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdatePlot() + + def AppendToOutput(self, text): + self.panelOutput.AppendText(''.join([text, '\n'])) + + def CreateApplicationIcon(self): + iconFile = 'resources' + os.sep + 'microscope.ico' + icon = wx.Icon(iconFile, wx.BITMAP_TYPE_ICO) + self.SetIcon(icon) + + def CreateCommandLine(self): + return wx.TextCtrl(self, -1, '', style=wx.NO_BORDER|wx.EXPAND) + + def CreatePanelAssistant(self): + panel = wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE) + panel.SetEditable(False) + return panel + + def CreatePanelCommands(self): + return hookecommands.Commands(self) + + def CreatePanelFolders(self): + #set file filters + filters = self.config['folders']['filters'] + index = self.config['folders'].as_int('filterindex') + #set initial directory + folder = self.config['general']['workdir'] + return wx.GenericDirCtrl(self, -1, dir=folder, size=(200, 250), style=wx.DIRCTRL_SHOW_FILTERS, filter=filters, defaultFilter=index) + + def CreatePanelOutput(self): + return wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE) + + def CreatePanelPlaylists(self): + return hookeplaylist.Playlists(self) + + def CreatePanelProperties(self): + return hookepropertyeditor.PropertyEditor(self) + + def CreatePanelResults(self): + return hookeresults.Results(self) + + def CreatePanelWelcome(self): + ctrl = wx.html.HtmlWindow(self, -1, wx.DefaultPosition, wx.Size(400, 300)) + introStr = '

Welcome to Hooke

' + \ + '

Features

' + \ + '' + \ + '

See the DocumentationIndex for more information

' + ctrl.SetPage(introStr) + return ctrl + + def CreateMenuBar(self): + menu_bar = wx.MenuBar() + #file + file_menu = wx.Menu() + file_menu.Append(wx.ID_OPEN, '&Open playlist\tCtrl-O') + file_menu.Append(wx.ID_SAVE, 'Save playlist\tCtrl-S') + file_menu.AppendSeparator() + file_menu.Append(wx.ID_EXIT, 'Exit\tCtrl-Q') + #edit + edit_menu = wx.Menu() + edit_menu.Append(ID_ExportText, 'Export text...') + edit_menu.Append(ID_ExportImage, 'Export image...') + edit_menu.AppendSeparator(); + edit_menu.Append(ID_Config, 'Preferences') + #view + view_menu = wx.Menu() + view_menu.AppendCheckItem(ID_ViewFolders, 'Folders\tF5') + view_menu.AppendCheckItem(ID_ViewPlaylists, 'Playlists\tF6') + view_menu.AppendCheckItem(ID_ViewCommands, 'Commands\tF7') + view_menu.AppendCheckItem(ID_ViewProperties, 'Properties\tF8') + view_menu.AppendCheckItem(ID_ViewAssistant, 'Assistant\tF9') + view_menu.AppendCheckItem(ID_ViewResults, 'Results\tF10') + view_menu.AppendCheckItem(ID_ViewOutput, 'Output\tF11') + #perspectives + self._perspectives_menu = self.CreatePerspectivesMenu() + #help + help_menu = wx.Menu() + help_menu.Append(wx.ID_ABOUT, 'About Hooke') + #put it all together + menu_bar.Append(file_menu, 'File') + menu_bar.Append(edit_menu, 'Edit') + menu_bar.Append(view_menu, 'View') + menu_bar.Append(self._perspectives_menu, "Perspectives") + menu_bar.Append(help_menu, 'Help') + + self.SetMenuBar(menu_bar) + + def CreateNotebook(self): + # create the notebook off-window to avoid flicker + client_size = self.GetClientSize() + ctrl = aui.AuiNotebook(self, -1, wx.Point(client_size.x, client_size.y), wx.Size(430, 200), self._notebook_style) + arts = [aui.AuiDefaultTabArt, aui.AuiSimpleTabArt, aui.VC71TabArt, aui.FF2TabArt, aui.VC8TabArt, aui.ChromeTabArt] + art = arts[self._notebook_theme]() + ctrl.SetArtProvider(art) + #uncomment if we find a nice icon + #page_bmp = wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16)) + ctrl.AddPage(self.CreatePanelWelcome(), "Welcome", False) + return ctrl + + def CreatePerspectivesMenu(self): + menu = wx.Menu() + menu.Append(ID_SavePerspective, "Save Perspective") + menu.Append(ID_DeletePerspective, "Delete Perspective") + menu.AppendSeparator() + #add perspectives to menubar and _perspectives + perspectivesDirectory = os.path.join(lh.hookeDir, 'perspectives') + if os.path.isdir(perspectivesDirectory): + perspectiveFileNames = os.listdir(perspectivesDirectory) + for perspectiveFilename in perspectiveFileNames: + filename = lh.get_file_path(perspectiveFilename, ['perspectives']) + if os.path.isfile(filename): + perspectiveFile = open(filename, 'rU') + perspective = perspectiveFile.readline() + perspectiveFile.close() + if perspective != '': + name, extension = os.path.splitext(perspectiveFilename) + if extension == '.txt': + menuItem = menu.AppendRadioItem(ID_FirstPerspective + len(self._perspectives), name) + self._perspectives[name] = [len(self._perspectives), perspective] + if self.config['perspectives']['active'] == name: + menuItem.Check() + #in case there are no perspectives + if not self._perspectives: + perspective = self._mgr.SavePerspective() + self.config['perspectives']['default'] = 'Default' + self._perspectives['Default'] = [0, perspective] + menuItem = menu.AppendRadioItem(ID_FirstPerspective, 'Default') + menuItem.Check() + self._SavePerspectiveToFile('Default', perspective) + return menu + + def CreateStatusbar(self): + statusbar = self.CreateStatusBar(2, wx.ST_SIZEGRIP) + statusbar.SetStatusWidths([-2, -3]) + statusbar.SetStatusText('Ready', 0) + welcomeString=u'Welcome to Hooke (version '+__version__+', '+__release_name__+')!' + statusbar.SetStatusText(welcomeString, 1) + return statusbar + + def CreateToolBar(self): + toolbar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.TB_FLAT | wx.TB_NODIVIDER) + toolbar.SetToolBitmapSize(wx.Size(16,16)) + toolbar_bmp1 = wx.ArtProvider_GetBitmap(wx.ART_QUESTION, wx.ART_OTHER, wx.Size(16, 16)) + toolbar_bmpOpen = wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, wx.Size(16, 16)) + toolbar_bmpSave = wx.ArtProvider_GetBitmap(wx.ART_FILE_SAVE, wx.ART_OTHER, wx.Size(16, 16)) + toolbar_bmpExportText = wx.ArtProvider_GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16)) + toolbar_bmpExportImage = wx.ArtProvider_GetBitmap(wx.ART_MISSING_IMAGE, wx.ART_OTHER, wx.Size(16, 16)) + toolbar.AddLabelTool(101, 'Open', toolbar_bmpOpen) + toolbar.AddLabelTool(102, 'Save', toolbar_bmpSave) + toolbar.AddSeparator() + toolbar.AddLabelTool(ID_ExportText, 'Export text...', toolbar_bmpExportText) + toolbar.AddLabelTool(ID_ExportImage, 'Export image...', toolbar_bmpExportImage) + toolbar.Realize() + return toolbar + + def CreateToolBarNavigation(self): + toolbar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.TB_FLAT | wx.TB_NODIVIDER) + toolbar.SetToolBitmapSize(wx.Size(16,16)) + toolbar_bmpBack = wx.ArtProvider_GetBitmap(wx.ART_GO_BACK, wx.ART_OTHER, wx.Size(16, 16)) + toolbar_bmpForward = wx.ArtProvider_GetBitmap(wx.ART_GO_FORWARD, wx.ART_OTHER, wx.Size(16, 16)) + toolbar.AddLabelTool(ID_Previous, 'Previous', toolbar_bmpBack, shortHelp='Previous curve') + toolbar.AddLabelTool(ID_Next, 'Next', toolbar_bmpForward, shortHelp='Next curve') + toolbar.Realize() + return toolbar + + def DeleteFromPlaylists(self, name): + if name in self.playlists: + del self.playlists[name] + tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem() + item, cookie = self.panelPlaylists.PlaylistsTree.GetFirstChild(tree_root) + while item.IsOk(): + playlist_name = self.panelPlaylists.PlaylistsTree.GetItemText(item) + if playlist_name == name: + try: + self.panelPlaylists.PlaylistsTree.Delete(item) + except: + pass + item = self.panelPlaylists.PlaylistsTree.GetNextSibling(item) + self.OnPlaylistsLeftDclick(None) + + def DeletePlotPage(self, name): + index = self._GetPlaylistTab(name) + plot = self.playlists[name][1] + plot = None + self.plotNotebook.RemovePage(index) + self.DeleteFromPlaylists(name) + + def GetActiveCurve(self): + playlist = self.GetActivePlaylist() + if playlist is not None: + return playlist.get_active_curve() + return None + + def GetActivePlaylist(self): + playlist_name = self._GetActivePlaylistName() + if playlist_name in self.playlists: + return self.playlists[playlist_name][0] + return None + + def GetActivePlot(self): + curve = self.GetActiveCurve() + if curve is not None: + return curve.plots[0] + return None + + def GetDockArt(self): + return self._mgr.GetArtProvider() + + def GetBoolFromConfig(self, *args): + if len(args) == 2: + plugin = args[0] + section = args[0] + key = args[1] + elif len(args) == 3: + plugin = args[0] + section = args[1] + key = args[2] + if self.configs.has_key(plugin): + config = self.configs[plugin] + return config[section][key].as_bool('value') + return None + + def GetFloatFromConfig(self, *args): + if len(args) == 2: + plugin = args[0] + section = args[0] + key = args[1] + elif len(args) == 3: + plugin = args[0] + section = args[1] + key = args[2] + if self.configs.has_key(plugin): + config = self.configs[plugin] + return config[section][key].as_float('value') + return None + + def GetIntFromConfig(self, *args): + if len(args) == 2: + plugin = args[0] + section = args[0] + key = args[1] + elif len(args) == 3: + plugin = args[0] + section = args[1] + key = args[2] + if self.configs.has_key(plugin): + config = self.configs[plugin] + return config[section][key].as_int('value') + return None + + def GetStringFromConfig(self, *args): + if len(args) == 2: + plugin = args[0] + section = args[0] + key = args[1] + elif len(args) == 3: + plugin = args[0] + section = args[1] + key = args[2] + if self.configs.has_key(plugin): + config = self.configs[plugin] + return config[section][key]['value'] + return None + + def GetPerspectiveMenuItem(self, name): + index = self._perspectives[name][0] + perspective_Id = ID_FirstPerspective + index + menu_item = self.MenuBar.FindItemById(perspective_Id) + return menu_item + + def HasPlotmanipulator(self, name): ''' - Smartish routine to create the menu from the self.menu_desc dictionary - Hope it's a workable solution for the future. + returns True if the plotmanipulator 'name' is loaded, False otherwise ''' - self.menus=[] #the menu objects to append to the menubar - self.menu_items={} #the single menu items dictionary, to bind to events - - names=self.menu_desc.keys() #we gotta sort, because iterating keys goes in odd order - names.sort() - - for name in names: - self.menus.append(wx.Menu()) - for menu_item in self.menu_desc[name]: - self.menu_items[menu_item[1]]=self.menus[-1].Append(-1, menu_item[0]) - - for menu,name in zip(self.menus,names): - self.Append(menu,name[2:]) - -class MainPanel(wx.Panel): - def __init__(self,parent,id): - - wx.Panel.__init__(self,parent,id) - self.splitter = wx.SplitterWindow(self) - -ID_FRAME=100 -class MainWindow(wx.Frame): - '''we make a frame inheriting wx.Frame and setting up things on the init''' - def __init__(self,parent,id,title): - - #----------------------------- - #WX WIDGETS INITIALIZATION - - wx.Frame.__init__(self,parent,ID_FRAME,title,size=(800,600),style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE) - - self.mainpanel=MainPanel(self,-1) - self.cpanels=[] - - self.cpanels.append(wx.Panel(self.mainpanel.splitter,-1)) - self.cpanels.append(wx.Panel(self.mainpanel.splitter,-1)) - - self.statusbar=wx.StatusBar(self,-1) - self.SetStatusBar(self.statusbar) - - self.mainmenubar=MainMenuBar() - self.SetMenuBar(self.mainmenubar) - - self.controls=[] - self.figures=[] - self.axes=[] - - #This is our matplotlib plot - self.controls.append(wxmpl.PlotPanel(self.cpanels[0],-1)) - self.controls.append(wxmpl.PlotPanel(self.cpanels[1],-1)) - #These are our figure and axes, so to have easy references - #Also, we initialize - self.figures=[control.get_figure() for control in self.controls] - self.axes=[figure.gca() for figure in self.figures] + for plotmanipulator in self.plotmanipulators: + if plotmanipulator[0] == name: + return True + return False + + def OnAbout(self, event): + msg = 'Hooke\n\n'+\ + 'A free, open source data analysis platform\n'+\ + '(c) 2006-2008 Massimo Sandal\n\n'+\ + '(c) 2009 Dr. Rolf Schmidt\n\n'+\ + 'Released under the GNU GPL v2' + dialog = wx.MessageDialog(self, msg, "About Hooke", wx.OK | wx.ICON_INFORMATION) + dialog.ShowModal() + dialog.Destroy() + + def OnClose(self, event): + #apply changes + self.config['main']['height'] = str(self.GetSize().GetHeight()) + self.config['main']['left'] = str(self.GetPosition()[0]) + self.config['main']['top'] = str(self.GetPosition()[1]) + self.config['main']['width'] = str(self.GetSize().GetWidth()) + # Writing the configuration file to 'hooke.ini' + self.config.write() + self._mgr.UnInit() + del self._mgr + self.Destroy() + + def OnDeletePerspective(self, event): + pass - for i in range(len(self.axes)): - self.axes[i].xaxis.set_major_formatter(EngrFormatter()) - self.axes[i].yaxis.set_major_formatter(EngrFormatter(2)) + def OnDirCtrlLeftDclick(self, event): + file_path = self.panelFolders.GetPath() + if os.path.isfile(file_path): + if file_path.endswith('.hkp'): + self.do_loadlist(file_path) + else: + pass + event.Skip() + def OnEraseBackground(self, event): + event.Skip() - self.cpanels[1].Hide() - self.mainpanel.splitter.Initialize(self.cpanels[0]) + def OnExecute(self, event): + item = self.panelCommands.CommandsTree.GetSelection() + if item.IsOk(): + if self.panelCommands.CommandsTree.ItemHasChildren(item): + pass + else: + #get the plugin + parent = self.panelCommands.CommandsTree.GetItemParent(item) + if not self.panelCommands.CommandsTree.ItemHasChildren(item): + parent_text = self.panelCommands.CommandsTree.GetItemText(parent) + item_text = self.panelCommands.CommandsTree.GetItemText(item) + if item_text in ['genlist', 'loadlist', 'savelist']: + property_values = self.panelProperties.GetPropertyValues() + arg_str = '' + for item in property_values: + arg_str = ''.join([arg_str, item, '=r"', str(property_values[item]), '", ']) + command = ''.join(['self.do_', item_text, '(', arg_str, ')']) + else: + command = ''.join(['self.do_', item_text, '()']) + exec(command) + pass - self.sizer_dance() #place/size the widgets + def OnExit(self, event): + self.Close() - self.controls[0].SetSize(self.cpanels[0].GetSize()) - self.controls[1].SetSize(self.cpanels[1].GetSize()) + def OnExportImage(self, event): + pass - #resize the frame to properly draw on Windows - frameSize=self.GetSize() - frameSize.DecBy(1, 1) - self.SetSize(frameSize) + def OnNext(self, event): ''' - #if you need the exact same size as before DecBy, uncomment this block - frameSize.IncBy(1, 1) - self.SetSize(frameSize) + NEXT + Go to the next curve in the playlist. + If we are at the last curve, we come back to the first. + ----- + Syntax: next, n ''' - - #------------------------------------------- - #NON-WX WIDGETS INITIALIZATION - - #Flags. - self.click_plot=0 - - #FIXME: These could become a single flag with different (string?) values - #self.on_measure_distance=False - #self.on_measure_force=False - - self.plot_fit=False - - #Number of points to be clicked - self.num_of_points = 2 - - #Data. + selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() + if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): + #GetFirstChild returns a tuple + #we only need the first element + next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(selected_item)[0] + else: + next_item = self.panelPlaylists.PlaylistsTree.GetNextSibling(selected_item) + if not next_item.IsOk(): + parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item) + #GetFirstChild returns a tuple + #we only need the first element + next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(parent_item)[0] + self.panelPlaylists.PlaylistsTree.SelectItem(next_item, True) + playlist = self.playlists[self._GetActivePlaylistName()][0] + if playlist.count > 1: + playlist.next() + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdatePlot() + + def OnNotebookPageClose(self, event): + ctrl = event.GetEventObject() + playlist_name = ctrl.GetPageText(ctrl._curpage) + self.DeleteFromPlaylists(playlist_name) + + def OnPaneClose(self, event): + event.Skip() + + def OnPlaylistsLeftDclick(self, event): + playlist_name = self._GetActivePlaylistName() + #if that playlist already exists + #we check if it is the active playlist (ie selected in panelPlaylists) + #and switch to it if necessary + if playlist_name in self.playlists: + index = self.plotNotebook.GetSelection() + current_playlist = self.plotNotebook.GetPageText(index) + #new_playlist = self.playlists[playlist_name][0] + #if current_playlist != new_playlist: + if current_playlist != playlist_name: + index = self._GetPlaylistTab(playlist_name) + self.plotNotebook.SetSelection(index) + #if a curve was double-clicked + item = self.panelPlaylists.PlaylistsTree.GetSelection() + #TODO: fix with get_active_curve + if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item): + index = self._GetActiveCurveIndex() + else: + index = 0 + if index >= 0: + playlist = self.playlists[playlist_name][0] + playlist.index = index + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdatePlot() + #if you uncomment the following line, the tree will collapse/expand as well + #event.Skip() + + def OnPlaylistsLeftDown(self, event): + hit_item, hit_flags = self.panelPlaylists.PlaylistsTree.HitTest(event.GetPosition()) + if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0: + #self.SetFocus() + self.panelPlaylists.PlaylistsTree.SelectItem(hit_item) + playlist_name = self._GetActivePlaylistName() + playlist = self.playlists[playlist_name][0] + #if a curve was clicked + item = self.panelPlaylists.PlaylistsTree.GetSelection() + if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item): + #TODO: fix with get_active_curve + index = self._GetActiveCurveIndex() + if index >= 0: + #playlist = self.playlists[playlist_name][0] + playlist.index = index + #self.playlists[playlist_name][0].index = index + #else: + ##self.playlists[playlist_name][0].index = 0 + #playlist.index = index + self.playlists[playlist_name][0] = playlist + event.Skip() + + def OnPrevious(self, event): ''' - self.current_x_ext=[[],[]] - self.current_y_ext=[[],[]] - self.current_x_ret=[[],[]] - self.current_y_ret=[[],[]] - - - self.current_x_unit=[None,None] - self.current_y_unit=[None,None] - ''' - - #Initialize xaxes, yaxes - #FIXME: should come from config - self.current_xaxes=0 - self.current_yaxes=0 - - #Other - - - self.index_buffer=[] + PREVIOUS + Go to the previous curve in the playlist. + If we are at the first curve, we jump to the last. + ------- + Syntax: previous, p + ''' + #playlist = self.playlists[self._GetActivePlaylistName()][0] + #select the previous curve and tell the user if we wrapped around + #self.AppendToOutput(playlist.previous()) + selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() + if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): + previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(selected_item) + else: + previous_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item) + if not previous_item.IsOk(): + parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item) + previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(parent_item) + self.panelPlaylists.PlaylistsTree.SelectItem(previous_item, True) + playlist = self.playlists[self._GetActivePlaylistName()][0] + if playlist.count > 1: + playlist.previous() + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdatePlot() + + def OnPropGridChanged (self, event): + prop = event.GetProperty() + if prop: + item_section = self.panelProperties.SelectedTreeItem + item_plugin = self.panelCommands.CommandsTree.GetItemParent(item_section) + plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin) + config = self.configs[plugin] + property_section = self.panelCommands.CommandsTree.GetItemText(item_section) + property_key = prop.GetName() + property_value = prop.GetValue() + config[property_section][property_key]['value'] = property_value + + def OnPropGridSelect(self, event): + pass - self.clicked_points=[] + def OnRestorePerspective(self, event): + name = self.MenuBar.FindItemById(event.GetId()).GetLabel() + self._mgr.LoadPerspective(self._perspectives[name][1]) + self.config['perspectives']['active'] = name + self._mgr.Update() + all_panes = self._mgr.GetAllPanes() + for pane in all_panes: + if not pane.name.startswith('toolbar'): + if pane.name == 'Assistant': + self.MenuBar.FindItemById(ID_ViewAssistant).Check(pane.window.IsShown()) + if pane.name == 'Folders': + self.MenuBar.FindItemById(ID_ViewFolders).Check(pane.window.IsShown()) + if pane.name == 'Playlists': + self.MenuBar.FindItemById(ID_ViewPlaylists).Check(pane.window.IsShown()) + if pane.name == 'Commands': + self.MenuBar.FindItemById(ID_ViewCommands).Check(pane.window.IsShown()) + if pane.name == 'Properties': + self.MenuBar.FindItemById(ID_ViewProperties).Check(pane.window.IsShown()) + if pane.name == 'Output': + self.MenuBar.FindItemById(ID_ViewOutput).Check(pane.window.IsShown()) + if pane.name == 'Results': + self.MenuBar.FindItemById(ID_ViewResults).Check(pane.window.IsShown()) + + def OnResultsCheck(self, index, flag): + curve = self.GetActiveCurve() + result = curve.data['results'][index]['visible'] = flag + self.UpdatePlot() + + def OnSavePerspective(self, event): + def nameExists(name): + for item in self._perspectives_menu.GetMenuItems(): + if item.GetText() == name: + return True + return False + + done = False + while not done: + dialog = wx.TextEntryDialog(self, 'Enter a name for the new perspective:', 'Save perspective') + dialog.SetValue('New perspective') + if dialog.ShowModal() != wx.ID_OK: + return + else: + name = dialog.GetValue() - self.measure_set=None + if nameExists(name): + dialogConfirm = wx.MessageDialog(self, 'A file with this name already exists.\n\nDo you want to replace it?', 'Confirm', wx.YES_NO|wx.ICON_QUESTION|wx.CENTER) + if dialogConfirm.ShowModal() == wx.ID_YES: + done = True + else: + done = True - self.events_from_gui = events_from_gui + perspective = self._mgr.SavePerspective() + if nameExists(name): + #check the corresponding menu item + menuItem = self.GetPerspectiveMenuItem(name) + #replace the perspectiveStr in _pespectives + index = self._perspectives[name][0] + self._perspectives[name] = [index, perspective] + else: + #simply add the new perspective to _perspectives + index = len(self._perspectives) + self._perspectives[name] = [len(self._perspectives), perspective] + menuItem = self._perspectives_menu.AppendRadioItem(ID_FirstPerspective + len(self._perspectives), name) + + menuItem.Check() + self._SavePerspectiveToFile(name, perspective) + #uncheck all perspective menu items + #as these are radio items, one item has to be checked at all times + #the line 'menuItem.Check()' above actually checks a second item + #but does not toggle + #so we need to uncheck all other items afterwards + #weirdly enough, menuitem.Toggle() doesn't do this properly either + for item in self._perspectives_menu.GetMenuItems(): + if item.IsCheckable(): + if item.GetLabel() != name: + item.Check(False) + + def OnView(self, event): + menu_id = event.GetId() + menu_item = self.MenuBar.FindItemById(menu_id) + menu_label = menu_item.GetLabel() + + pane = self._mgr.GetPane(menu_label) + pane.Show(not pane.IsShown()) + #if we don't do the following, the Folders pane does not resize properly on hide/show + if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked(): + #folders_size = pane.GetSize() + self.panelFolders.Fit() + self._mgr.Update() + + def OnSize(self, event): + event.Skip() + + def OnTreeCtrlCommandsLeftDown(self, event): + hit_item, hit_flags = self.panelCommands.CommandsTree.HitTest(event.GetPosition()) + if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0: + self.panelCommands.CommandsTree.SelectItem(hit_item) + self.panelProperties.SelectedTreeItem = hit_item + #if a command was clicked + properties = [] + if not self.panelCommands.CommandsTree.ItemHasChildren(hit_item): + item_plugin = self.panelCommands.CommandsTree.GetItemParent(hit_item) + plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin) + if self.configs.has_key(plugin): + #config = self.panelCommands.CommandsTree.GetPyData(item_plugin) + config = self.configs[plugin] + section = self.panelCommands.CommandsTree.GetItemText(hit_item) + #display docstring in help window + doc_string = eval('self.do_' + section + '.__doc__') + if section in config: + for option in config[section]: + properties.append([option, config[section][option]]) + else: + module = self.panelCommands.CommandsTree.GetItemText(hit_item) + if module != 'general': + doc_string = eval('plugins.' + module + '.' + module + 'Commands.__doc__') + else: + doc_string = 'The module "general" contains Hooke core functionality' + if doc_string is not None: + self.panelAssistant.ChangeValue(doc_string) + hookepropertyeditor.PropertyEditor.Initialize(self.panelProperties, properties) + event.Skip() + + def UpdatePlaylists(self): + #setup the playlist in the Playlist tree + tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem() + playlist_root = self.panelPlaylists.PlaylistsTree.AppendItem(tree_root, playlist.name, 0) + #add all curves to the Playlist tree + curves = {} + for index, curve in enumerate(playlist.curves): + ##remove the extension from the name of the curve + ##TODO: optional? + #item_text, extension = os.path.splitext(curve.name) + #curve_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, item_text, 1) + curve_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, curve.name, 1) + if index == playlist.index: + self.panelPlaylists.PlaylistsTree.SelectItem(curve_ID) + #create the plot tab and add playlist to the dictionary + plotPanel = wxmpl.PlotPanel(self, ID_FirstPlot + len(self.playlists)) + notebook_tab = self.plotNotebook.AddPage(plotPanel, playlist.name, True) + #tab_index = self.plotNotebook.GetSelection() + figure = plotPanel.get_figure() + #self.playlists[playlist.name] = [playlist, tab_index, figure] + self.playlists[playlist.name] = [playlist, figure] + self.panelPlaylists.PlaylistsTree.Expand(playlist_root) + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdatePlot() + +#HELPER FUNCTIONS +#Everything sending an event should be here + def _measure_N_points(self, N, whatset=1): ''' - This dictionary keeps all the flags and the relative functon names that - have to be called when a point is clicked. - That is: - - if point is clicked AND foo_flag=True - - foo() - - Conversely, foo_flag is True if a corresponding event is launched by the CLI. - - self.ClickedPoints() takes care of handling this - ''' - - self.click_flags_functions={'measure_points':[False, 'MeasurePoints']} - - #Binding of custom events from CLI --> GUI functions! - #FIXME: Should use the self.Bind() syntax - EVT_PLOT(self, self.PlotCurve) - EVT_PLOT_CONTACT(self, self.PlotContact) - EVT_GET_DISPLAYED_PLOT(self, self.OnGetDisplayedPlot) - EVT_MEASURE_POINTS(self, self.OnMeasurePoints) - EVT_EXPORT_IMAGE(self,self.ExportImage) - EVT_CLOSE_PLOT(self, self.OnClosePlot) - EVT_SHOW_PLOTS(self, self.OnShowPlots) - - #This event and control decide what happens when I click on the plot 0. - wxmpl.EVT_POINT(self, self.controls[0].GetId(), self.ClickPoint0) - wxmpl.EVT_POINT(self, self.controls[1].GetId(), self.ClickPoint1) - - #RUN PLUGIN-SPECIFIC INITIALIZATION - #make sure we execute _plug_init() for every command line plugin we import - for plugin_name in config['plugins']: + general helper function for N-points measures + ''' + wx.PostEvent(self.frame,self.list_of_events['measure_points'](num_of_points=N, set=whatset)) + while 1: try: - hooke_module=__import__('hooke.plugin.'+plugin_name) - plugin = getattr(hooke_module.plugin, plugin_name) - try: - eval('plugin.'+plugin_name+'Gui._plug_init(self)') - pass - except AttributeError: - pass - except ImportError: + points=self.frame.events_from_gui.get() + break + except Empty: pass + return points - - - #WX-SPECIFIC FUNCTIONS - def sizer_dance(self): + def _clickize(self, xvector, yvector, index): ''' - adjust size and placement of wxpython widgets. - ''' - self.splittersizer = wx.BoxSizer(wx.VERTICAL) - self.splittersizer.Add(self.mainpanel.splitter, 1, wx.EXPAND) - - self.plot1sizer = wx.BoxSizer() - self.plot1sizer.Add(self.controls[0], 1, wx.EXPAND) - - self.plot2sizer = wx.BoxSizer() - self.plot2sizer.Add(self.controls[1], 1, wx.EXPAND) - - self.panelsizer=wx.BoxSizer() - self.panelsizer.Add(self.mainpanel, -1, wx.EXPAND) - - self.cpanels[0].SetSizer(self.plot1sizer) - self.cpanels[1].SetSizer(self.plot2sizer) - - self.mainpanel.SetSizer(self.splittersizer) - self.SetSizer(self.panelsizer) - - def binding_dance(self): - self.Bind(wx.EVT_MENU, self.OnOpenPlayMenu, self.menubar.menu_items['openplaymenu']) - self.Bind(wx.EVT_MENU, self.OnExitMenu, self.menubar.menu_items['exitmenu']) - self.Bind(wx.EVT_MENU, self.OnExportText, self.menubar.menu_items['exporttextmenu']) - self.Bind(wx.EVT_MENU, self.OnExportImage, self.menubar.menu_items['exportimagemenu']) - self.Bind(wx.EVT_MENU, self.OnAboutMenu, self.menubar.menu_items['aboutmenu']) - - # DOUBLE PLOT MANAGEMENT - #---------------------- - def show_both(self): + returns a ClickedPoint() object from an index and vectors of x, y coordinates ''' - Shows both plots. - ''' - self.mainpanel.splitter.SplitHorizontally(self.cpanels[0],self.cpanels[1]) - self.mainpanel.splitter.SetSashGravity(0.5) - self.mainpanel.splitter.SetSashPosition(300) #FIXME: we should get it and restore it - self.mainpanel.splitter.UpdateSize() - - def close_plot(self,plot): + point = lh.ClickedPoint() + point.index = index + point.absolute_coords = xvector[index], yvector[index] + point.find_graph_coords(xvector, yvector) + return point + +#PLAYLIST INTERACTION COMMANDS +#------------------------------- + def do_genlist(self, folder=lh.hookeDir, filemask='*.*'): ''' - Closes one plot - only if it's open - ''' - if not self.cpanels[plot].IsShown(): - return - if plot != 0: - self.current_plot_dest = 0 + GENLIST + Generates a file playlist. + Note it doesn't *save* it: see savelist for this. + + If [input files] is a directory, it will use all files in the directory for playlist. + So: + genlist dir + genlist dir/ + genlist dir/*.* + + are all equivalent syntax. + ------------ + Syntax: genlist [input files] + ''' + #args list is: input folder, file mask + if os.path.isdir(folder): + path = os.path.join(folder, filemask) + #expanding correctly the input list with the glob module :) + files = glob.glob(path) + files.sort() + #TODO: change cursor or progressbar (maybe in statusbar) + #self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + playlist = playlist.Playlist(self.drivers) + for item in files: + curve = playlist.add_curve(item) + plot = copy.deepcopy(curve.plots[0]) + #add the 'raw' data + curve.add_data('raw', plot.vectors[0][0], plot.vectors[0][1], color=plot.colors[0], style='plot') + curve.add_data('raw', plot.vectors[1][0], plot.vectors[1][1], color=plot.colors[1], style='plot') + #apply all active plotmanipulators and add the 'manipulated' data + for plotmanipulator in self.plotmanipulators: + plot = plotmanipulator[1](plot, curve) + curve.set_data('manipulated', plot.vectors[0][0], plot.vectors[0][1], color=plot.colors[0], style='plot') + curve.add_data('manipulated', plot.vectors[1][0], plot.vectors[1][1], color=plot.colors[1], style='plot') + if playlist.count > 0: + playlist.name = self._GetUniquePlaylistName(os.path.basename(folder)) + playlist.reset() + self.AddToPlaylists(playlist) + self.AppendToOutput(playlist.get_status_string()) else: - self.current_plot_dest = 1 - self.cpanels[plot].Hide() - self.mainpanel.splitter.Unsplit(self.cpanels[plot]) - self.mainpanel.splitter.UpdateSize() - - - def OnClosePlot(self,event): - self.close_plot(event.to_close) - - def OnShowPlots(self,event): - self.show_both() + self.AppendToOutput(''.join(['Cannot find folder ', folder])) - - #FILE MENU FUNCTIONS - #-------------------- - def OnOpenPlayMenu(self, event): - pass - - def OnExitMenu(self,event): - pass - - def OnExportText(self,event): - pass - - def OnExportImage(self,event): - pass - - def OnAboutMenu(self,event): - pass - - #PLOT INTERACTION - #---------------- - def PlotCurve(self,event): + def do_loadlist(self, filename): + ''' + LOADLIST + Loads a file playlist + ----------- + Syntax: loadlist [playlist file] ''' - plots the current ext,ret curve. - ''' - dest=0 - - #FIXME: BAD kludge following. There should be a well made plot queue mechanism, with replacements etc. - #--- - #If we have only one plot in the event, we already have one in self.plots and this is a secondary plot, - #do not erase self.plots but append the new plot to it. - if len(event.plots) == 1 and event.plots[0].destination != 0 and len(self.plots) == 1: - self.plots.append(event.plots[0]) - #if we already have two plots and a new secondary plot comes, we substitute the previous - if len(event.plots) == 1 and event.plots[0].destination != 0 and len(self.plots) > 1: - self.plots[1] = event.plots[0] + #TODO: check for duplicate playlists, ask the user for a unique name + #if self.playlist_name in self.playlists: + + #add hkp extension if necessary + if not filename.endswith('.hkp'): + filename = ''.join([filename, '.hkp']) + #prefix with 'hookeDir' if just a filename or a relative path + filename = lh.get_file_path(filename) + if os.path.isfile(filename): + #TODO: change cursor + #self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + playlist_new = playlist.Playlist(self.drivers) + playlist_new.load(filename) + if playlist_new.count > 0: + for curve in playlist_new.curves: + plot = copy.deepcopy(curve.plots[0]) + for plotmanip in self.plotmanipulators: + #to_plot = plotmanip[1](to_plot, curve) + plot = plotmanip[1](plot, curve) + curve.set_data('manipulated', plot.vectors[0][0], plot.vectors[0][1], color=plot.colors[0], style='plot') + curve.add_data('manipulated', plot.vectors[1][0], plot.vectors[1][1], color=plot.colors[1], style='plot') + self.AddToPlaylists(playlist_new) + #else: + ##TODO: display dialog + self.AppendToOutput(playlist_new.get_status_string()) + #TODO: change cursor + #self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) else: - self.plots = event.plots - - #FIXME. Should be in PlotObject, somehow - c=0 - for plot in self.plots: - if self.plots[c].styles==[]: - self.plots[c].styles=[None for item in plot.vectors] - if self.plots[c].colors==[]: - self.plots[c].colors=[None for item in plot.vectors] - - for plot in self.plots: - ''' - MAIN LOOP FOR ALL PLOTS (now only 2 are allowed but...) - ''' - if 'destination' in dir(plot): - dest=plot.destination - - #if the requested panel is not shown, show it - if not ( self.cpanels[dest].IsShown() ): - self.show_both() - - self.axes[dest].hold(False) - self.current_vectors=plot.vectors - self.current_title=plot.title - self.current_plot_dest=dest #let's try this way to take into account the destination plot... - - c=0 - - if len(plot.colors)==0: - plot.colors=[None] * len(plot.vectors) - if len(plot.styles)==0: - plot.styles=[None] * len(plot.vectors) - - for vectors_to_plot in self.current_vectors: - if plot.styles[c]=='scatter': - if plot.colors[c]==None: - self.axes[dest].scatter(vectors_to_plot[0], vectors_to_plot[1]) - else: - self.axes[dest].scatter(vectors_to_plot[0], vectors_to_plot[1],color=plot.colors[c]) - else: - if plot.colors[c]==None: - self.axes[dest].plot(vectors_to_plot[0], vectors_to_plot[1]) - else: - self.axes[dest].plot(vectors_to_plot[0], vectors_to_plot[1], color=plot.colors[c]) - self.axes[dest].hold(True) - c+=1 - - ''' - for vectors_to_plot in self.current_vectors: - if len(vectors_to_plot)==2: #3d plots are to come... - if len(plot.styles) > 0 and plot.styles[c] == 'scatter': - self.axes[dest].scatter(vectors_to_plot[0],vectors_to_plot[1]) - elif len(plot.styles) > 0 and plot.styles[c] == 'scatter_red': - self.axes[dest].scatter(vectors_to_plot[0],vectors_to_plot[1],color='red') - else: - self.axes[dest].plot(vectors_to_plot[0],vectors_to_plot[1]) - - self.axes[dest].hold(True) - c+=1 - else: - pass - ''' - #FIXME: tackles only 2d plots - self.axes[dest].set_xlabel(plot.units[0]) - self.axes[dest].set_ylabel(plot.units[1]) - - #FIXME: set smaller fonts - self.axes[dest].set_title(plot.title) - - if plot.xaxes: - #swap X axis - xlim=self.axes[dest].get_xlim() - self.axes[dest].set_xlim((xlim[1],xlim[0])) - if plot.yaxes: - #swap Y axis - ylim=self.axes[dest].get_ylim() - self.axes[dest].set_ylim((ylim[1],ylim[0])) + #TODO: display dialog + self.AppendToOutput(''.join['File ', filename, ' not found.\n']) + pass - for i in range(len(self.axes)): - self.axes[i].xaxis.set_major_formatter(EngrFormatter()) - self.axes[i].yaxis.set_major_formatter(EngrFormatter(2)) + def do_savelist(self, filename): + ''' + SAVELIST + Saves the current file playlist on disk. + ------------ + Syntax: savelist [filename] + ''' + #self.playlist_generics['pointer'] = self._GetActiveCurveIndex + pointer = self._GetActiveCurveIndex() + #autocomplete filename if not specified + if not filename.endswith('.hkp'): + filename = filename.join(['.hkp']) + + playlist = self.GetActivePlaylist() + playlist.set_XML() + playlist.save(filename) + +#PLOT INTERACTION COMMANDS +#------------------------------- + def UpdatePlot(self): + def add_plot(plot): + if plot['visible'] and plot['x'] and plot['y']: + color = plot['color'] + style = plot['style'] + if style == 'plot': + axes.plot(plot['x'], plot['y'], color=color, zorder=1) + if style == 'scatter': + axes.scatter(plot['x'], plot['y'], color=color, s=0.5, zorder=2) + + def add_plot2(plot): + if plot.visible and plot.x and plot.y: + if plot.style == 'plot': + axes.plot(plot.x, plot.y, color=plot.color, zorder=1) + if plot.style == 'scatter': + axes.scatter(plot.x, plot.y, color=plot.color, s=0.5, zorder=2) + + playlist_name = self._GetActivePlaylistName() + index = self._GetActiveCurveIndex() + playlist = self.playlists[playlist_name][0] + curve = playlist.get_active_curve() + plot = playlist.get_active_plot() + figure = self.playlists[playlist_name][1] + + figure.clf() + exclude = None + if curve.data.has_key('manipulated'): + exclude = 'raw' + elif curve.data.has_key('raw'): + exclude = 'manipulated' + + if exclude is not None: + #TODO: what is this good for? + if not hasattr(self, 'subplot'): + axes = figure.add_subplot(111) + + axes.set_title(plot.title) + axes.set_xlabel(plot.units[0]) + axes.set_ylabel(plot.units[1]) + + for set_of_plots in curve.data: + if set_of_plots != exclude: + plots = curve.data[set_of_plots] + for each_plot in plots: + add_plot(each_plot) + + #TODO: add multiple results support + #for fit in curve.fits: + if curve.fits.has_key('wlc'): + for plot in curve.fits['wlc'].results: + add_plot2(plot) + self.panelResults.DisplayResults(curve.fits['wlc']) + else: + self.panelResults.ClearResults() - self.controls[dest].draw() + axes.figure.canvas.draw() + else: + self.AppendToOutput('Not able to plot.') - def PlotContact(self,event): - ''' - plots the contact point - DEPRECATED! - ''' - self.axes[0].hold(True) - self.current_contact_index=event.contact_index +ID_PaneBorderSize = wx.ID_HIGHEST + 1 +ID_SashSize = ID_PaneBorderSize + 1 +ID_CaptionSize = ID_PaneBorderSize + 2 +ID_BackgroundColor = ID_PaneBorderSize + 3 +ID_SashColor = ID_PaneBorderSize + 4 +ID_InactiveCaptionColor = ID_PaneBorderSize + 5 +ID_InactiveCaptionGradientColor = ID_PaneBorderSize + 6 +ID_InactiveCaptionTextColor = ID_PaneBorderSize + 7 +ID_ActiveCaptionColor = ID_PaneBorderSize + 8 +ID_ActiveCaptionGradientColor = ID_PaneBorderSize + 9 +ID_ActiveCaptionTextColor = ID_PaneBorderSize + 10 +ID_BorderColor = ID_PaneBorderSize + 11 +ID_GripperColor = ID_PaneBorderSize + 12 - #now we fake a clicked point - self.clicked_points.append(ClickedPoint()) - self.clicked_points[-1].absolute_coords=self.current_x_ret[dest][self.current_contact_index], self.current_y_ret[dest][self.current_contact_index] - self.clicked_points[-1].is_marker=True - self._replot() - self.clicked_points=[] +#---------------------------------------------------------------------- - def OnMeasurePoints(self,event): - ''' - trigger flags to measure N points - ''' - self.click_flags_functions['measure_points'][0]=True - if 'num_of_points' in dir(event): - self.num_of_points=event.num_of_points - if 'set' in dir(event): - self.measure_set=event.set - - def ClickPoint0(self,event): - self.current_plot_dest=0 - self.ClickPoint(event) - def ClickPoint1(self,event): - self.current_plot_dest=1 - self.ClickPoint(event) - - def ClickPoint(self,event): - ''' - this function decides what to do when we receive a left click on the axes. - We trigger other functions: - - the action chosen by the CLI sends an event - - the event raises a flag : self.click_flags_functions['foo'][0] - - the raised flag wants the function in self.click_flags_functions[1] to be called after a click - ''' - for key, value in self.click_flags_functions.items(): - if value[0]: - eval('self.'+value[1]+'(event)') - - - - def MeasurePoints(self,event,current_set=1): - dest=self.current_plot_dest - try: - current_set=self.measure_set - except AttributeError: - pass - - #find the current plot matching the clicked destination - plot=self._plot_of_dest() - if len(plot.vectors)-1 < current_set: #what happens if current_set is 1 and we have only 1 vector? - current_set=current_set-len(plot.vectors) - - xvector=plot.vectors[current_set][0] - yvector=plot.vectors[current_set][1] - - self.clicked_points.append(ClickedPoint()) - self.clicked_points[-1].absolute_coords=event.xdata, event.ydata - self.clicked_points[-1].find_graph_coords(xvector,yvector) - self.clicked_points[-1].is_marker=True - self.clicked_points[-1].is_line_edge=True - self.clicked_points[-1].dest=dest - - self._replot() - - if len(self.clicked_points)==self.num_of_points: - self.events_from_gui.put(self.clicked_points) - #restore to default state: - self.clicked_points=[] - self.click_flags_functions['measure_points'][0]=False - - - def OnGetDisplayedPlot(self,event): - if 'dest' in dir(event): - self.GetDisplayedPlot(event.dest) - else: - self.GetDisplayedPlot(self.current_plot_dest) +if __name__ == '__main__': - def GetDisplayedPlot(self,dest): - ''' - returns to the CLI the currently displayed plot for the given destination - ''' - displayed_plot=self._plot_of_dest(dest) - events_from_gui.put(displayed_plot) + ## now, silence a deprecation warning for py2.3 + #import warnings + #warnings.filterwarnings("ignore", "integer", DeprecationWarning, "wxPython.gdi") - def ExportImage(self,event): - ''' - exports an image as a file. - Current supported file formats: png, eps - (matplotlib docs say that jpeg should be supported too, but with .jpg it doesn't work for me!) - ''' - #dest=self.current_plot_dest - dest=event.dest - filename=event.name - self.figures[dest].savefig(filename) - - ''' - def _find_nearest_point(self, mypoint, dataset=1): - - #Given a clicked point on the plot, finds the nearest point in the dataset (in X) that - #corresponds to the clicked point. - - dest=self.current_plot_dest - - xvector=plot.vectors[dataset][0] - yvector=plot.vectors[dataset][1] - - #Ye Olde sorting algorithm... - #FIXME: is there a better solution? - index=0 - best_index=0 - best_diff=10^9 #hope we never go over this magic number :( - for point in xvector: - diff=abs(point-mypoint) - if diff 0 and plot.styles[c]=='scatter': - self.axes[dest].scatter(plotset[0], plotset[1],color=plot.colors[c]) - elif len(plot.styles) > 0 and plot.styles[c] == 'scatter_red': - self.axes[dest].scatter(plotset[0],plotset[1],color='red') - else: - self.axes[dest].plot(plotset[0], plotset[1]) - ''' - c+=1 - #plot points we have clicked - for item in self.clicked_points: - if item.is_marker: - if item.graph_coords==(None,None): #if we have no graph coords, we display absolute coords - self.axes[dest].scatter([item.absolute_coords[0]],[item.absolute_coords[1]]) - else: - self.axes[dest].scatter([item.graph_coords[0]],[item.graph_coords[1]]) - - if self.plot_fit: - print 'DEBUGGING WARNING: use of self.plot_fit is deprecated!' - self.axes[dest].plot(self.plot_fit[0],self.plot_fit[1]) - - self.axes[dest].hold(True) - #set old axes again - self.axes[dest].set_xlim(xlim) - self.axes[dest].set_ylim(ylim) - #set title and names again... - self.axes[dest].set_title(self.current_title) - self.axes[dest].set_xlabel(plot.units[0]) - self.axes[dest].set_ylabel(plot.units[1]) - #and redraw! - self.controls[dest].draw() - - -class MySplashScreen(wx.SplashScreen): - """ - Create a splash screen widget. - That's just a fancy addition... every serious application has a splash screen! - """ - def __init__(self, frame): - # This is a recipe to a the screen. - # Modify the following variables as necessary. - #aBitmap = wx.Image(name = "wxPyWiki.jpg").ConvertToBitmap() - aBitmap=wx.Image(name=os.path.join( - config['install']['docpath'], - 'hooke.jpg')).ConvertToBitmap() - splashStyle = wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT - splashDuration = 2000 # milliseconds - splashCallback = None - # Call the constructor with the above arguments in exactly the - # following order. - wx.SplashScreen.__init__(self, aBitmap, splashStyle, - splashDuration, None, -1) - wx.EVT_CLOSE(self, self.OnExit) - self.frame=frame - wx.Yield() - - def OnExit(self, evt): - self.Hide() - - self.frame.Show() - # The program will freeze without this line. - evt.Skip() # Make sure the default handler runs too... - - -#------------------------------------------------------------------------------ - -def main(): - app=wx.PySimpleApp() - - def make_gui_class(*bases): - return type(MainWindow)("MainWindowPlugged", bases + (MainWindow,), {}) - - main_frame = make_gui_class(*GUI_PLUGINS)(None, -1, ('Hooke '+__version__)) - - #FIXME. The frame.Show() is called by the splashscreen here! Ugly as hell. - - mysplash=MySplashScreen(main_frame) - mysplash.Show() - - my_cmdline=CliThread(main_frame, list_of_events) - my_cmdline.start() + redirect=True + if __debug__: + redirect=False + app = Hooke(redirect=redirect) app.MainLoop() -if __name__ == '__main__': - main() + diff --git a/hooke/libhooke.py b/hooke/libhooke.py index eb4ce79..a1d623f 100644 --- a/hooke/libhooke.py +++ b/hooke/libhooke.py @@ -19,307 +19,23 @@ import os import os.path import string import csv -from matplotlib.ticker import ScalarFormatter -from . import libhookecurve as lhc - -HOOKE_VERSION=['0.8.3_devel', 'Seinei', '2008-04-16'] +HOOKE_VERSION=['0.9.0_devel', 'Kenzo', '2009-09-xx'] WX_GOOD=['2.6','2.8'] +hookeDir='' -class PlaylistXML(object): - ''' - This module allows for import/export of an XML playlist into/out of a list of HookeCurve objects - ''' - - def __init__(self): - - self.playlist=None #the DOM object representing the playlist data structure - self.playpath=None #the path of the playlist XML file - self.plaything=None - self.hidden_attributes=['curve'] #This list contains hidden attributes that we don't want to go into the playlist. - - def export(self, list_of_hooke_curves, generics): - ''' - Creates an initial playlist from a list of files. - A playlist is an XML document with the following syntaxis: - - - - - ''' - - #create the output playlist, a simple XML document - impl=xml.dom.minidom.getDOMImplementation() - #create the document DOM object and the root element - newdoc=impl.createDocument(None, "playlist",None) - top_element=newdoc.documentElement - - #save generics variables - playlist_generics=newdoc.createElement("generics") - top_element.appendChild(playlist_generics) - for key in generics.keys(): - newdoc.createAttribute(key) - playlist_generics.setAttribute(key,str(generics[key])) - - #save curves and their attributes - for item in list_of_hooke_curves: - #playlist_element=newdoc.createElement("curve") - playlist_element=newdoc.createElement("element") - top_element.appendChild(playlist_element) - for key in item.__dict__: - if not (key in self.hidden_attributes): - newdoc.createAttribute(key) - playlist_element.setAttribute(key,str(item.__dict__[key])) - - self.playlist=newdoc - - def load(self,filename): - ''' - loads a playlist file - ''' - myplay=file(filename) - self.playpath=filename - - #the following 3 lines are needed to strip newlines. otherwise, since newlines - #are XML elements too (why?), the parser would read them (and re-save them, multiplying - #newlines...) - #yes, I'm an XML n00b - the_file=myplay.read() - the_file_lines=the_file.split('\n') - the_file=''.join(the_file_lines) - - self.playlist=xml.dom.minidom.parseString(the_file) - #inner parsing functions - def handlePlaylist(playlist): - list_of_files=playlist.getElementsByTagName("element") - generics=playlist.getElementsByTagName("generics") - return handleFiles(list_of_files), handleGenerics(generics) +def get_file_path(filename, folders = []): + if os.path.dirname(filename) == '' or os.path.isabs(filename) == False: + path = '' + for folder in folders: + path = os.path.join(path, folder) + filename = os.path.join(hookeDir, path, filename) - def handleGenerics(generics): - generics_dict={} - if len(generics)==0: - return generics_dict + return filename - for attribute in generics[0].attributes.keys(): - generics_dict[attribute]=generics[0].getAttribute(attribute) - return generics_dict - - def handleFiles(list_of_files): - new_playlist=[] - for myfile in list_of_files: - #rebuild a data structure from the xml attributes - the_curve=lhc.HookeCurve( - os.path.join(os.path.dirname(self.playpath), - myfile.getAttribute('path'))) - for attribute in myfile.attributes.keys(): - #extract attributes for the single curve - if attribute == 'path': - continue # we already added this attribute - the_curve.__dict__[attribute]=myfile.getAttribute(attribute) - new_playlist.append(the_curve) - - return new_playlist #this is the true thing returned at the end of this function...(FIXME: clarity) - - return handlePlaylist(self.playlist) - - - def save(self,output_filename): - ''' - saves the playlist in a XML file. - ''' - try: - outfile=file(output_filename,'w') - except IOError: - print 'libhooke.py : Cannot save playlist. Wrong path or filename' - return - - self.playlist.writexml(outfile,indent='\n') - outfile.close() - -def config_file_path(filename, config_dir=None): - if config_dir == None: - config_dir = os.path.abspath( - os.path.join(os.path.dirname(os.path.dirname(__file__)), 'conf')) - return os.path.join(config_dir, filename) - -class HookeConfig(object): - ''' - Handling of Hooke configuration file - - Mostly based on the simple-yet-useful examples of the Python Library Reference - about xml.dom.minidom - - FIXME: starting to look a mess, should require refactoring - ''' - - def __init__(self, config_dir=None): - self.config={} - self.config['install']={} - self.config['plugins']=[] - self.config['drivers']=[] - self.config['plotmanips']=[] - self.config_dir = config_dir - - def load_config(self, filename): - myconfig=file(config_file_path(filename, config_dir=self.config_dir)) - - #the following 3 lines are needed to strip newlines. otherwise, since newlines - #are XML elements too, the parser would read them (and re-save them, multiplying - #newlines...) - #yes, I'm an XML n00b - the_file=myconfig.read() - the_file_lines=the_file.split('\n') - the_file=''.join(the_file_lines) - - self.config_tree=xml.dom.minidom.parseString(the_file) - - def getText(nodelist): - #take the text from a nodelist - #from Python Library Reference 13.7.2 - rc = '' - for node in nodelist: - if node.nodeType == node.TEXT_NODE: - rc += node.data - return rc - - def handleConfig(config): - install_elements=config.getElementsByTagName("install") - display_elements=config.getElementsByTagName("display") - plugins_elements=config.getElementsByTagName("plugins") - drivers_elements=config.getElementsByTagName("drivers") - defaultlist_elements=config.getElementsByTagName("defaultlist") - plotmanip_elements=config.getElementsByTagName("plotmanips") - handleInstall(install_elements) - handleDisplay(display_elements) - handlePlugins(plugins_elements) - handleDrivers(drivers_elements) - handleDefaultlist(defaultlist_elements) - handlePlotmanip(plotmanip_elements) - - def handleInstall(install_elements): - for install in install_elements: - for node in install.childNodes: - if node.nodeType == node.TEXT_NODE: - continue - path = os.path.abspath(getText(node.childNodes).strip()) - self.config['install'][str(node.tagName)] = path - - def handleDisplay(display_elements): - for element in display_elements: - for attribute in element.attributes.keys(): - self.config[attribute]=element.getAttribute(attribute) - - def handlePlugins(plugins): - for plugin in plugins[0].childNodes: - try: - self.config['plugins'].append(str(plugin.tagName)) - except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... - pass - #FIXME: code duplication - def handleDrivers(drivers): - for driver in drivers[0].childNodes: - try: - self.config['drivers'].append(str(driver.tagName)) - except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... - pass - - def handlePlotmanip(plotmanips): - for plotmanip in plotmanips[0].childNodes: - try: - self.config['plotmanips'].append(str(plotmanip.tagName)) - except: #if we allow fancy formatting of xml, there is a text node, so tagName fails for it... - pass - - def handleDefaultlist(defaultlist): - ''' - default playlist - ''' - dflist=getText(defaultlist[0].childNodes) - self.config['defaultlist']=dflist.strip() - - handleConfig(self.config_tree) - #making items in the dictionary more machine-readable - for item in self.config.keys(): - try: - self.config[item]=float(self.config[item]) - except TypeError: #we are dealing with a list, probably. keep it this way. - try: - self.config[item]=eval(self.config[item]) - except: #not a list, not a tuple, probably a string? - pass - except ValueError: #if we can't get it to a number, it must be None or a string - if string.lower(self.config[item])=='none': - self.config[item]=None - else: - pass - - return self.config - - - def save_config(self, config_filename): - print 'Not Implemented.' - pass - - -class EngrFormatter(ScalarFormatter): - """A variation of the standard ScalarFormatter, using only multiples of -three -in the mantissa. A fixed number of decimals can be displayed with the optional -parameter `ndec` . If `ndec` is None (default), the number of decimals is -defined -from the current ticks. - """ - def __init__(self, ndec=None, useOffset=True, useMathText=False): - ScalarFormatter.__init__(self, useOffset, useMathText) - if ndec is None or ndec < 0: - self.format = None - elif ndec == 0: - self.format = "%d" - else: - self.format = "%%1.%if" % ndec - #........................ - - def _set_orderOfMagnitude(self, mrange): - """Sets the order of magnitude.""" - locs = numpy.absolute(self.locs) - if self.offset: - oom = numpy.floor(numpy.log10(mrange)) - else: - if locs[0] > locs[-1]: - val = locs[0] - else: - val = locs[-1] - if val == 0: - oom = 0 - else: - oom = numpy.floor(numpy.log10(val)) - if oom <= -3: - self.orderOfMagnitude = 3*(oom//3) - elif oom <= -1: - self.orderOfMagnitude = -3 - elif oom >= 4: - self.orderOfMagnitude = 3*(oom//3) - else: - self.orderOfMagnitude = 0 - - - #........................ - def _set_format(self): - """Sets the format string to format all ticklabels.""" - # set the format string to format all the ticklabels - locs = (numpy.array(self.locs)-self.offset) / 10**self.orderOfMagnitude+1e-15 - sigfigs = [len(str('%1.3f'% loc).split('.')[1].rstrip('0')) \ - for loc in locs] - sigfigs.sort() - if self.format is None: - self.format = '%1.' + str(sigfigs[-1]) + 'f' - if self._usetex or self._useMathText: self.format = '$%s$'%self.format - - - -class ClickedPoint: +class ClickedPoint(object): ''' this class defines what a clicked point on the curve plot is ''' @@ -341,13 +57,12 @@ class ClickedPoint: ''' #FIXME: a general algorithm using min() is needed! - #print '---DEPRECATED FIND_GRAPH_COORDS_OLD---' best_index=0 best_dist=10**9 #should be more than enough given the scale for index in scipy.arange(1,len(xvector),1): dist=((self.absolute_coords[0]-xvector[index])**2)+(100*((self.absolute_coords[1]-yvector[index])))**2 - #TODO, generalize? y coordinate is multiplied by 100 due to scale differences in the plot + #TODO, generalize? y coordinate is multiplied by 100 due to scale differences in the plot if dist= 0 and index < self.count: + self.curves.remove(index) + + def filter_curves(self, keeper_fn=labmda curve:True): + playlist = copy.deepcopy(self) + for curve in reversed(playlist.curves): + if not keeper_fn(curve): + playlist.curves.remove(curve) + try: # attempt to maintain the same active curve + playlist.index = playlist.curves.index(self.get_active_curve()) + except ValueError: + playlist.index = 0 + playlist._saved = False + playlist.count = len(playlist.curves) + return playlist + + def get_active_curve(self): + return self.curves[self.index] + + #TODO: do we need this? + def get_active_plot(self): + return self.curves[self.index].plots[0] + + def get_status_string(self): + if self.has_curves() + return '%s (%s/%s)' % (self.name, self.index + 1, self.count) + return 'The file %s does not contain any valid force curve data.' \ + % self.name + + def has_curves(self): + if self.count > 0: + return True + return False + + def is_saved(self): + return self._saved + + def load(self, path): + ''' + loads a playlist file + ''' + self.path = path + self.name = os.path.basename(path) + playlist = lh.delete_empty_lines_from_xmlfile(path) + self.xml = xml.dom.minidom.parse(path) + # Strip blank spaces: + self._removeWhitespaceNodes() + + generics_list = self.xml.getElementsByTagName('generics') + curve_list = self.xml.getElementsByTagName('curve') + self._loadGenerics(generics_list) + self._loadCurves(curve_list) + self._saved = True + + def _removeWhitespaceNodes(self, root_node=None): + if root_node == None: + root_node = self.xml + for node in root_node.childNodes: + if node.nodeType == node.TEXT_NODE and node.data.strip() == '': + root_node.removeChild(node) # drop this whitespace node + else: + _removeWhitespaceNodes(root_node=node) # recurse down a level + + def _loadGenerics(self, generics_list, clear=True): + if clear: + self.genericsDict = {} + #populate generics + generics_list = self.xml.getElementsByTagName('generics') + for generics in generics_list: + for attribute in generics.attributes.keys(): + self.genericsDict[attribute] = generics_list[0].getAttribute(attribute) + if self.genericsDict.has_key('pointer'): + index = int(self.genericsDict['pointer']) + if index >= 0 and index < len(self.curves): + self.index = index + else: + index = 0 + + def _loadCurves(self, curve_list, clear=True): + if clear: + self.curves = [] + #populate playlist with curves + for curve in curve_list: + #rebuild a data structure from the xml attributes + curve_path = lh.get_file_path(element.getAttribute('path')) + #extract attributes for the single curve + attributes = dict([(k,curve.getAttribute(k)) + for k in curve.attributes.keys()]) + attributes.pop('path') + curve = self.add_curve(os.path.join(path, curve_path), attributes) + if curve is not None: + for plot in curve.plots: + curve.add_data('raw', plot.vectors[0][0], plot.vectors[0][1], color=plot.colors[0], style='plot') + curve.add_data('raw', plot.vectors[1][0], plot.vectors[1][1], color=plot.colors[1], style='plot') + + def next(self): + self.index += 1 + if self.index > self.count - 1: + self.index = 0 + + def previous(self): + self.index -= 1 + if self.index < 0: + self.index = self.count - 1 + + def reset(self): + if self.has_curves(): + self.index = 0 + else: + self.index = None + + def save(self, path): + ''' + saves the playlist in a XML file. + ''' + try: + output_file = file(path, 'w') + except IOError, e: + #TODO: send message + print 'Cannot save playlist: %s' % e + return + self.xml.writexml(output_file, indent='\n') + output_file.close() + self._saved = True + + def set_XML(self): + ''' + Creates an initial playlist from a list of files. + A playlist is an XML document with the following syntax: + + + + + + + Relative paths are interpreted relative to the location of the + playlist file. + ''' + #create the output playlist, a simple XML document + implementation = xml.dom.minidom.getDOMImplementation() + #create the document DOM object and the root element + self.xml = implementation.createDocument(None, 'playlist', None) + root = self.xml.documentElement + + #save generics variables + playlist_generics = self.xml.createElement('generics') + root.appendChild(playlist_generics) + self.genericsDict['pointer'] = self.index + for key in self.genericsDict.keys(): + self.xml.createAttribute(key) + playlist_generics.setAttribute(key, str(self.genericsDict[key])) + + #save curves and their attributes + for item in self.curves: + playlist_curve = self.xml.createElement('curve') + root.appendChild(playlist_curve) + for key in item.__dict__: + if not (key in self.hiddenAttributes): + self.xml.createAttribute(key) + playlist_curve.setAttribute(key, str(item.__dict__[key])) + self._saved = False diff --git a/hooke/plugin/__init__.py b/hooke/plugin/__init__.py index e69de29..bd85e3c 100644 --- a/hooke/plugin/__init__.py +++ b/hooke/plugin/__init__.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +''' +Commands and settings panel for Hooke + +Displays commands and settings for Hooke in a tree control +(c) Dr. Rolf Schmidt, 2009 +''' + +from configobj import ConfigObj +import os.path +from validate import Validator +import wx + +import libhooke as lh + +class Commands(wx.Panel): + + def __init__(self, parent): + # Use the WANTS_CHARS style so the panel doesn't eat the Return key. + wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS|wx.NO_BORDER, size=(160, 200)) + + self.CommandsTree = wx.TreeCtrl(self, -1, wx.Point(0, 0), wx.Size(160, 250), wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.TR_HIDE_ROOT) + imglist = wx.ImageList(16, 16, True, 2) + imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16))) + imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_EXECUTABLE_FILE, wx.ART_OTHER, wx.Size(16, 16))) + self.CommandsTree.AssignImageList(imglist) + self.CommandsTree.AddRoot('Commands and Settings', 0) + + self.ExecuteButton = wx.Button(self, -1, 'Execute') + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.CommandsTree, 1, wx.EXPAND) + sizer.Add(self.ExecuteButton, 0, wx.EXPAND) + + self.SetSizer(sizer) + sizer.Fit(self) + + def Initialize(self, plugins): + tree_root = self.CommandsTree.GetRootItem() + for plugin in plugins: + filename = ''.join([plugin, '.ini']) + path = lh.get_file_path(filename, ['plugins']) + config = ConfigObj() + if os.path.isfile(path): + config.filename = path + config.reload() + #append the ini file to the plugin + plugin_root = self.CommandsTree.AppendItem(tree_root, plugin, 0, data=wx.TreeItemData(config)) + else: + plugin_root = self.CommandsTree.AppendItem(tree_root, plugin, 0) + + #add all commands to the tree + for command in plugins[plugin]: + command_label = command.replace('do_', '') + #do not add the ini file to the command (we'll access the ini file of the plugin (ie parent) instead, see above) + self.CommandsTree.AppendItem(plugin_root, command_label, 1) + self.CommandsTree.Expand(plugin_root) diff --git a/hooke/plugin/autopeak.py b/hooke/plugin/autopeak.py index 50dc9e3..ab6117e 100644 --- a/hooke/plugin/autopeak.py +++ b/hooke/plugin/autopeak.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from hooke.libhooke import WX_GOOD, ClickedPoint +from hooke.libhooke import WX_GOOD import wxversion wxversion.select(WX_GOOD) @@ -14,10 +14,14 @@ import time import warnings warnings.simplefilter('ignore',np.RankWarning) +#from .. import ui.gui.results as results -class autopeakCommands(object): - def do_autopeak(self,args): +class autopeakCommands(object): + ''' + TODO: autopeak docstring. + ''' + def do_autopeak(self, args): ''' AUTOPEAK (autopeak.py) diff --git a/hooke/plugin/flatfilts-rolf.py b/hooke/plugin/flatfilts-rolf.py new file mode 100644 index 0000000..2c06ae9 --- /dev/null +++ b/hooke/plugin/flatfilts-rolf.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python + +''' +FLATFILTS + +Force spectroscopy curves filtering of flat curves +Licensed under the GNU LGPL version 2 + +Other plugin dependencies: +procplots.py (plot processing plugin) +''' + +import xml.dom.minidom + +import wx +#import scipy +import numpy +from numpy import diff +import os.path + +#import pickle + +import libpeakspot as lps +#import libhookecurve as lhc +import hookecurve as lhc +import libhooke as lh +import wxversion +wxversion.select(lh.WX_GOOD) + + +class flatfiltsCommands: + + def do_flatfilt(self): + ''' + FLATFILT + (flatfilts.py) + Filters out flat (featureless) curves of the current playlist, + creating a playlist containing only the curves with potential + features. + ------------ + Syntax: + flatfilt [min_npks min_deviation] + + min_npks = minmum number of points over the deviation + (default=4) + + min_deviation = minimum signal/noise ratio + (default=9) + + If called without arguments, it uses default values, that + should work most of the times. + ''' + #TODO: should this be optional? + medianfilter = 7 + + self.AppendToOutput('Processing playlist...') + self.AppendToOutput('(Please wait)') + features = [] + playlist = self.GetActivePlaylist() + curves = playlist.curves + curve_index = 0 + for curve in curves: + curve_index += 1 + try: + notflat = self.has_features(curve) + feature_string = '' + if notflat != 1: + if notflat > 0: + feature_string = str(notflat) + ' features' + else: + feature_string = 'no features' + else: + feature_string = '1 feature' + output_string = ''.join(['Curve ', curve.name, '(', str(curve_index), '/', str(len(curves)), '): ', feature_string]) + except: + notflat = False + output_string = ''.join(['Curve ', curve.name, '(', str(curve_index), '/', str(len(curves)), '): cannot be filtered. Probably unable to retrieve force data from corrupt file.']) + self.AppendToOutput(output_string) + if notflat: + curve.features = notflat + features.append(curve_index - 1) + if not features: + self.AppendToOutput('Found nothing interesting. Check the playlist, could be a bug or criteria could be too stringent.') + else: + if len(features) < playlist.count: + self.AppendToOutput(''.join(['Found ', str(len(features)), ' potentially interesting curves.'])) + self.AppendToOutput('Regenerating playlist...') + playlist_filtered = playlist.filter_curves(features) + self.AddPlaylist(playlist_filtered, name='flatfilt') + else: + self.AppendToOutput('No curves filtered. Try different filtering criteria.') + + def has_features(self, curve): + ''' + decides if a curve is flat enough to be rejected from analysis: it sees if there + are at least min_npks points that are higher than min_deviation times the absolute value + of noise. + + Algorithm original idea by Francesco Musiani, with my tweaks and corrections. + ''' + medianfilter = 7 + mindeviation = self.GetIntFromConfig('flatfilts', 'flatfilt', 'min_deviation') + minpeaks = self.GetIntFromConfig('flatfilts', 'flatfilt', 'min_npks') + #medianfilter = self.GetIntFromConfig('flatfilt', 'median_filter') + #mindeviation = self.GetIntFromConfig('convfilt', 'mindeviation') + #minpeaks = self.GetIntFromConfig('convfilt', 'minpeaks') + + retvalue = 0 + + #item.identify(self.drivers) + #we assume the first is the plot with the force curve + #do the median to better resolve features from noise + flat_plot = self.plotmanip_median(curve.driver.default_plots()[0], curve, customvalue=medianfilter) + flat_vects = flat_plot.vectors + curve.driver.close_all() + #needed to avoid *big* memory leaks! + #del item.driver + #del item + + #absolute value of derivate + yretdiff=diff(flat_vects[1][1]) + yretdiff=[abs(value) for value in yretdiff] + #average of derivate values + diffmean=numpy.mean(yretdiff) + yretdiff.sort() + yretdiff.reverse() + c_pks=0 + for value in yretdiff: + if value/diffmean > mindeviation: + c_pks += 1 + else: + break + + if c_pks >= minpeaks: + retvalue = c_pks + + del flat_plot, flat_vects, yretdiff + + return retvalue + + ################################################################ + #-----CONVFILT------------------------------------------------- + #-----Convolution-based peak recognition and filtering. + #Requires the libpeakspot.py library + + def has_peaks(self, plot, curve=None): + ''' + Finds peak position in a force curve. + FIXME: should be moved to libpeakspot.py + ''' + + blindwindow = self.GetFloatFromConfig('flatfilts', 'convfilt', 'blindwindow') + #need to convert the string that contains the list into a list + convolution = eval(self.GetStringFromConfig('flatfilts', 'convfilt', 'convolution')) + maxcut = self.GetFloatFromConfig('flatfilts', 'convfilt', 'maxcut') + mindeviation = self.GetFloatFromConfig('flatfilts', 'convfilt', 'mindeviation') + positive = self.GetBoolFromConfig('flatfilts', 'convfilt', 'positive') + seedouble = self.GetIntFromConfig('flatfilts', 'convfilt', 'seedouble') + stable = self.GetFloatFromConfig('flatfilts', 'convfilt', 'stable') + + xret = plot.vectors[1][0] + yret = plot.vectors[1][1] + #Calculate convolution + convoluted = lps.conv_dx(yret, convolution) + + #surely cut everything before the contact point + cut_index = self.find_contact_point(plot, curve) + #cut even more, before the blind window + start_x = xret[cut_index] + blind_index = 0 + for value in xret[cut_index:]: + if abs((value) - (start_x)) > blindwindow * (10 ** -9): + break + blind_index += 1 + cut_index += blind_index + #do the dirty convolution-peak finding stuff + noise_level = lps.noise_absdev(convoluted[cut_index:], positive, maxcut, stable) + above = lps.abovenoise(convoluted, noise_level, cut_index, mindeviation) + peak_location, peak_size = lps.find_peaks(above, seedouble=seedouble) + #take the maximum + for i in range(len(peak_location)): + peak = peak_location[i] + maxpk = min(yret[peak - 10:peak + 10]) + index_maxpk = yret[peak - 10:peak + 10].index(maxpk) + (peak - 10) + peak_location[i] = index_maxpk + + return peak_location, peak_size + + def exec_has_peaks(self, curve): + ''' + encapsulates has_peaks for the purpose of correctly treating the curve objects in the convfilt loop, + to avoid memory leaks + ''' + #item.identify(self.drivers) + #we assume the first is the plot with the force curve + plot = curve.driver.default_plots()[0] + + if self.HasPlotmanipulator('plotmanip_flatten'): + #If flatten is present, use it for better recognition of peaks... + #flatten = self._find_plotmanip('flatten') #extract flatten plot manipulator + #plot = flatten(plot, item, customvalue=1) + plot = self.plotmanip_flatten(plot, curve, customvalue=1) + + peak_location, peak_size = self.has_peaks(plot, curve) + #close all open files + curve.driver.close_all() + #needed to avoid *big* memory leaks! + #del item.driver + #del item + return peak_location, peak_size + + #------------------------ + #------commands---------- + #------------------------ + def do_peaks(self): + ''' + PEAKS + (flatfilts.py) + Test command for convolution filter / test. + ---- + Syntax: peaks [deviations] + absolute deviation = number of times the convolution signal is above the noise absolute deviation. + Default is 5. + ''' + + #TODO: check if the following line gives us what we need + curve = self.GetActiveCurve() + defplots = curve.driver.default_plots()[0] #we need the raw, uncorrected plots + + #if 'flatten' in self.config['plotmanips']: + if self.HasPlotmanipulator('plotmanip_flatten'): + #flatten=self._find_plotmanip('flatten') #extract flatten plot manipulator + #defplots=flatten(defplots, self.current) + defplots = self.plotmanip_flatten(defplots, curve, customvalue=0) + else: + self.AppendToOutput('The flatten plot manipulator is not loaded. Enabling it could give better results.') + + peak_location, peak_size = self.has_peaks(defplots, curve) + self.AppendToOutput('Found ' + str(len(peak_location)) + ' peaks.') + self.AppendToOutput('peaks ' + curve.filename + ' ' + str(len(peak_location))) + #to_dump = 'peaks ' + current_curve.filename + ' ' + str(len(peak_location)) + #self.outlet.push(to_dump) + + #if no peaks, we have nothing to plot. exit. + if peak_location: + #otherwise, we plot the peak locations. + xplotted_ret = curve.plots[0].vectors[1][0] + yplotted_ret = curve.plots[0].vectors[1][1] + xgood = [xplotted_ret[index] for index in peak_location] + ygood = [yplotted_ret[index] for index in peak_location] + + curve.plots[0].add_set(xgood, ygood) + curve.plots[0].styles.append('scatter') + curve.plots[0].colors.append('indigo') + + #recplot = self._get_displayed_plot() + #recplot.vectors.append([xgood,ygood]) + #if recplot.styles == []: + #recplot.styles = [None, None, 'scatter'] + #recplot.colors = [None, None, None] + #else: + #recplot.styles += ['scatter'] + #recplot.colors += [None] + + #self._send_plot([recplot]) + self.UpdatePlot() + + def do_convfilt(self): + ''' + CONVFILT + (flatfilts.py) + Filters out flat (featureless) curves of the current playlist, + creating a playlist containing only the curves with potential + features. + ------------ + Syntax: + convfilt [min_npks min_deviation] + + min_npks = minmum number of peaks + (to set the default, see convfilt.conf file; CONVCONF and SETCONF commands) + + min_deviation = minimum signal/noise ratio *in the convolution* + (to set the default, see convfilt.conf file; CONVCONF and SETCONF commands) + + If called without arguments, it uses default values. + ''' + + self.AppendToOutput('Processing playlist...') + self.AppendToOutput('(Please wait)') + minpeaks = self.GetIntFromConfig('flatfilts', 'convfilt', 'minpeaks') + features = [] + playlist = self.GetActivePlaylist() + + curves = self.GetActivePlaylist().curves + curve_index = 0 + for curve in curves: + curve_index += 1 + try: + peak_location, peak_size = self.exec_has_peaks(curve) + number_of_peaks = len(peak_location) + if number_of_peaks != 1: + if number_of_peaks > 0: + feature_string = str(number_of_peaks) + ' features' + else: + feature_string = 'no features' + else: + feature_string = '1 feature' + if number_of_peaks >= minpeaks: + feature_string += '+' + output_string = ''.join(['Curve ', curve.name, '(', str(curve_index), '/', str(len(curves)), '): ', feature_string]) + except: + peak_location = [] + peak_size = [] + output_string = ''.join(['Curve ', curve.name, '(', str(curve_index), '/', str(len(curves)), '): cannot be filtered. Probably unable to retrieve force data from corrupt file.']) + self.AppendToOutput(output_string) + if number_of_peaks >= minpeaks: + curve.peak_location = peak_location + curve.peak_size = peak_size + features.append(curve_index - 1) + + #TODO: do we need this? Flattening might not be necessary/desired + #Warn that no flattening had been done. + if not self.HasPlotmanipulator('plotmanip_flatten'): + self.AppendToOutput('Flatten manipulator was not found. Processing was done without flattening.') + self.AppendToOutput('Try to enable it in the configuration file for better results.') + if not features: + self.AppendToOutput('Found nothing interesting. Check the playlist, could be a bug or criteria could be too stringent.') + else: + if len(features) < playlist.count: + self.AppendToOutput(''.join(['Found ', str(len(features)), ' potentially interesting curves.'])) + self.AppendToOutput('Regenerating playlist...') + playlist_filtered = playlist.filter_curves(features) + self.AddPlaylist(playlist_filtered, name='convfilt') + else: + self.AppendToOutput('No curves filtered. Try different filtering criteria.') + diff --git a/hooke/plugin/generalclamp.py b/hooke/plugin/generalclamp.py index 2777f49..b3a6126 100644 --- a/hooke/plugin/generalclamp.py +++ b/hooke/plugin/generalclamp.py @@ -185,7 +185,7 @@ class generalclampCommands(object): return else: print 'Found',len(notflat_list),'potentially interesting curves.' - print 'Regenerating Playlist...' + print 'Regenerating playlist...' self.pointer=0 self.current_list=notflat_list self.current=self.current_list[self.pointer] diff --git a/hooke/plugin/generalvclamp.py b/hooke/plugin/generalvclamp.py index 5f6657b..ef644c0 100644 --- a/hooke/plugin/generalvclamp.py +++ b/hooke/plugin/generalvclamp.py @@ -281,6 +281,12 @@ class generalvclampCommands(object): print 'Cannot fit. Did you click twice the same point?' return + # Outputs the relevant slope parameter + print 'Slope:' + print str(parameters[0]) + to_dump='slope '+self.curve.path+' '+str(parameters[0]) + self.outlet.push(to_dump) + # Makes a vector with the fitted parameters and sends it to the GUI xtoplot=parameters[2] ytoplot=[] diff --git a/hooke/plugin/procplots.py b/hooke/plugin/procplots.py index b7826ae..12136a6 100644 --- a/hooke/plugin/procplots.py +++ b/hooke/plugin/procplots.py @@ -53,7 +53,7 @@ class procplotsCommands(object): return dplot - def do_subtplot(self,args): + def do_subtplot(self, args): ''' SUBTPLOT (procplots.py plugin) diff --git a/hooke/plugin/showconvoluted.py b/hooke/plugin/showconvoluted.py new file mode 100644 index 0000000..a2de586 --- /dev/null +++ b/hooke/plugin/showconvoluted.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +''' +SHOW CONVOLUTED CURVE PLUGIN FOR HOOKE + +This plugin contains a plotmanipulator to show the convoluted curve. +(c) Dr. Rolf Schmidt, 2009 +''' + +import libpeakspot + +class showconvolutedCommands: + + #def _plug_init(self): + #pass + + def plotmanip_showconvoluted(self, plot, curve): + ''' + BEGIN: taken from procplots.py + might need some tweaking + ''' + #use only for force spectroscopy experiments! + if curve.driver.experiment != 'smfs': + return plot + + ''' + END: taken from procplots.py + ''' + + #need to convert the string that contains the list into a list + #convolution = eval(self.config['convfilt']['convolution']['value']) + convolution = eval(self.GetStringFromConfig('flatfilts', 'convfilt', 'convolution')) + + xRet = plot.vectors[1][0] + yRet = plot.vectors[1][1] + convoluted = libpeakspot.conv_dx(yRet, convolution) + #convoluted=libpeakspot.conv_dx(yRet, [-20, -10, -6, 0, 12, 12, 12]) + plot.add_set(xRet, convoluted) + #plot.vectors[1][1]=[i for i in convoluted] + #set contact point plot style to 'plot' + #and the color to red + plot.styles.append('plot') + plot.colors.append('black') + #peak_location, peak_size = self.has_peaks(plot, blindwindow, convolution, minpeaks) + peak_locations, peak_sizes = self.has_peaks(plot, curve) + + if peak_locations: + peak_locations_x = [] + peak_locations_y = [] + for location in peak_locations: + peak_locations_x.append(xRet[location]) + peak_locations_y.append(yRet[location]) + plot.add_set(peak_locations_x, peak_locations_y) + plot.styles.append('scatter') + plot.colors.append('green') + plot.add_set(peak_locations_x, peak_sizes) + plot.styles.append('scatter') + plot.colors.append('magenta') + + #Return the plot object. + return plot diff --git a/hooke/test/test.hkp b/hooke/test/test.hkp index 883d597..7a6c881 100644 --- a/hooke/test/test.hkp +++ b/hooke/test/test.hkp @@ -1,5 +1,5 @@ - + - - + + diff --git a/hooke/ui/gui/hookeplaylist.py b/hooke/ui/gui/hookeplaylist.py new file mode 100644 index 0000000..34b267f --- /dev/null +++ b/hooke/ui/gui/hookeplaylist.py @@ -0,0 +1,106 @@ +#import os +#import os.path +import wx +#import xml.dom.minidom + +class Playlists(wx.Panel): + + def __init__(self, parent): + # Use the WANTS_CHARS style so the panel doesn't eat the Return key. + wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS|wx.NO_BORDER, size=(160, 200)) + + self.PlaylistsTree = wx.TreeCtrl(self, -1, wx.Point(0, 0), wx.Size(160, 250), wx.TR_DEFAULT_STYLE | wx.NO_BORDER | wx.TR_HIDE_ROOT) + imglist = wx.ImageList(16, 16, True, 2) + imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16))) + imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16))) + self.PlaylistsTree.AssignImageList(imglist) + self.PlaylistsTree.AddRoot('Playlists', 0) + self.PlaylistsTree.Bind(wx.EVT_RIGHT_DOWN , self.OnContextMenu) + + self.Playlists = {} + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.PlaylistsTree, 1, wx.EXPAND) + self.SetSizer(sizer) + sizer.Fit(self) + + #def add_playlist(self, files=[], name='Untitled'): + ##TODO: change cursor or progressbar (maybe in statusbar) + ##self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + #if files: + #playlist = hookeplaylist.Playlist(self.drivers) + #for item in files: + #playlist.add_curve(item) + #if playlist.count > 0: + #playlist_name = name + #count = 1 + #while playlist_name in self.Playlists: + #playlist_name = ''.join([name, str(count)]) + #count += 1 + #playlist.name = playlist_name + #playlist.reset() + #self.AddToPlaylists(playlist) + + #def FilterPlaylist(self, curves_to_keep=[]): + #playlist_active = self.GetActivePlaylist() + #playlist_new = Playlist(self.drivers) + #for curve_index in curves_to_keep: + #playlist_new.curves.append(playlist_active.curves[curve_index]) + #return playlist_new + + def GetActivePlaylist(self): + playlist_name = self.GetActivePlaylistName() + if playlist_name in self.playlists: + return self.playlists[playlist_name][0] + else: + return None + + def GetActivePlaylistName(self): + #get the selected item from the tree + selected_item = self.PlaylistsTree.GetSelection() + #test if a playlist or a curve was double-clicked + if self.PlaylistsTree.ItemHasChildren(selected_item): + playlist_item = selected_item + else: + #get the name of the playlist + playlist_item = self.PlaylistsTree.GetItemParent(selected_item) + #now we have a playlist + return self.PlaylistsTree.GetItemText(playlist_item) + + def OnContextMenu(self, event): + hit_item, hit_flags = self.PlaylistsTree.HitTest(event.GetPosition()) + if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0: + self.PlaylistsTree.SelectItem(hit_item) + # only do this part the first time so the events are only bound once + # + # Yet another alternate way to do IDs. Some prefer them up top to + # avoid clutter, some prefer them close to the object of interest + # for clarity. + if not hasattr(self, 'ID_popupAdd'): + self.ID_popupAdd = wx.NewId() + self.ID_popupClose = wx.NewId() + self.Bind(wx.EVT_MENU, self.OnPopupAdd, id=self.ID_popupAdd) + self.Bind(wx.EVT_MENU, self.OnPopupClose, id=self.ID_popupClose) + # make a menu + menu = wx.Menu() + items = [['Add', self.ID_popupAdd] , ['Close', self.ID_popupClose]] + for item in items: + menu.Append(item[1], item[0]) + # Popup the menu. If an item is selected then its handler + # will be called before PopupMenu returns. + self.PopupMenu(menu) + menu.Destroy() + + def OnPopupAdd(self, event): + pass + + def OnPopupClose(self, event): + item = self.PlaylistsTree.GetSelection() + if self.PlaylistsTree.ItemHasChildren(item): + playlist_name = self.PlaylistsTree.GetItemText(item) + self.Parent.DeletePlotPage(playlist_name) + #del self.Playlists[playlist_name] + #TODO: delete playlist, close notebook tab + #self.Parent.AddToPlaylists() + else: + pass diff --git a/hooke/ui/gui/hookepropertyeditor.py b/hooke/ui/gui/hookepropertyeditor.py new file mode 100644 index 0000000..2854e36 --- /dev/null +++ b/hooke/ui/gui/hookepropertyeditor.py @@ -0,0 +1,519 @@ +import sys +import time +import math +import os.path +import pdb + +import wx +import wx.propgrid as wxpg +import wx.stc + + +class Display: + property_descriptor = [] + def __init__(self): + pass + + +class ValueObject: + def __init__(self): + pass + + +class IntProperty2(wxpg.PyProperty): + """\ + This is a simple re-implementation of wxIntProperty. + """ + def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=0): + wxpg.PyProperty.__init__(self, label, name) + self.SetValue(value) + + def GetClassName(self): + """\ + This is not 100% necessary and in future is probably going to be + automated to return class name. + """ + return "IntProperty2" + + def GetEditor(self): + return "TextCtrl" + + def GetValueAsString(self, flags): + return str(self.GetValue()) + + def PyStringToValue(self, s, flags): + try: + v = int(s) + if self.GetValue() != v: + return v + except TypeError: + if flags & wxpg.PG_REPORT_ERROR: + wx.MessageBox("Cannot convert '%s' into a number."%s, "Error") + return False + + def PyIntToValue(self, v, flags): + if (self.GetValue() != v): + return v + + +class PyFilesProperty(wxpg.PyArrayStringProperty): + def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=[]): + wxpg.PyArrayStringProperty.__init__(self, label, name, value) + self.SetValue(value) + + def OnSetValue(self, v): + self.value = v + self.display = ', '.join(self.value) + + def GetValueAsString(self, argFlags): + return self.display + + def PyStringToValue(self, s, flags): + return [a.strip() for a in text.split(',')] + + def OnEvent(self, propgrid, ctrl, event): + if event.GetEventType() == wx.wxEVT_COMMAND_BUTTON_CLICKED: + # Show dialog to select a string, call DoSetValue and + # return True, if value changed. + return True + + return False + + +class PyObjectPropertyValue: + """\ + Value type of our sample PyObjectProperty. We keep a simple dash-delimited + list of string given as argument to constructor. + """ + def __init__(self, s=None): + try: + self.ls = [a.strip() for a in s.split('-')] + except: + self.ls = [] + + def __repr__(self): + return ' - '.join(self.ls) + + + +class PyObjectProperty(wxpg.PyProperty): + """\ + Another simple example. This time our value is a PyObject (NOTE: we can't + return an arbitrary python object in DoGetValue. It cannot be a simple + type such as int, bool, double, or string, nor an array or wxObject based. + Dictionary, None, or any user-specified Python object is allowed). + """ + def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=None): + wxpg.PyProperty.__init__(self, label, name) + self.SetValue(value) + + def GetClassName(self): + return self.__class__.__name__ + + def GetEditor(self): + return "TextCtrl" + + def GetValueAsString(self, flags): + return repr(self.GetValue()) + + def PyStringToValue(self, s, flags): + return PyObjectPropertyValue(s) + + +class ShapeProperty(wxpg.PyEnumProperty): + """\ + Demonstrates use of OnCustomPaint method. + """ + def __init__(self, label, name = wxpg.LABEL_AS_NAME, value=-1): + wxpg.PyEnumProperty.__init__(self, label, name, ['Line','Circle','Rectangle'], [0,1,2], value) + + def OnMeasureImage(self, index): + return wxpg.DEFAULT_IMAGE_SIZE + + def OnCustomPaint(self, dc, rect, paint_data): + """\ + paint_data.m_choiceItem is -1 if we are painting the control, + in which case we need to get the drawn item using DoGetValue. + """ + item = paint_data.m_choiceItem + if item == -1: + item = self.DoGetValue() + + dc.SetPen(wx.Pen(wx.BLACK)) + dc.SetBrush(wx.Brush(wx.BLACK)) + + if item == 0: + dc.DrawLine(rect.x,rect.y,rect.x+rect.width,rect.y+rect.height) + elif item == 1: + half_width = rect.width / 2 + dc.DrawCircle(rect.x+half_width,rect.y+half_width,half_width-3) + elif item == 2: + dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) + + +class LargeImagePickerCtrl(wx.Window): + """\ + Control created and used by LargeImageEditor. + """ + def __init__(self): + pre = wx.PreWindow() + self.PostCreate(pre) + + def Create(self, parent, id_, pos, size, style = 0): + wx.Window.Create(self, parent, id_, pos, size, style | wx.BORDER_SIMPLE) + img_spc = size[1] + self.tc = wx.TextCtrl(self, -1, "", (img_spc,0), (2048,size[1]), wx.BORDER_NONE) + self.SetBackgroundColour(wx.WHITE) + self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) + self.property = None + self.bmp = None + self.Bind(wx.EVT_PAINT, self.OnPaint) + + def OnPaint(self, event): + dc = wx.BufferedPaintDC(self) + + whiteBrush = wx.Brush(wx.WHITE) + dc.SetBackground(whiteBrush) + dc.Clear() + + bmp = self.bmp + if bmp: + dc.DrawBitmap(bmp, 2, 2) + else: + dc.SetPen(wx.Pen(wx.BLACK)) + dc.SetBrush(whiteBrush) + dc.DrawRectangle(2, 2, 64, 64) + + def RefreshThumbnail(self): + """\ + We use here very simple image scaling code. + """ + if not self.property: + self.bmp = None + return + + path = self.property.DoGetValue() + + if not os.path.isfile(path): + self.bmp = None + return + + image = wx.Image(path) + image.Rescale(64, 64) + self.bmp = wx.BitmapFromImage(image) + + def SetProperty(self, property): + self.property = property + self.tc.SetValue(property.GetDisplayedString()) + self.RefreshThumbnail() + + def SetValue(self, s): + self.RefreshThumbnail() + self.tc.SetValue(s) + + def GetLastPosition(self): + return self.tc.GetLastPosition() + + +class LargeImageEditor(wxpg.PyEditor): + """\ + Double-height text-editor with image in front. + """ + def __init__(self): + wxpg.PyEditor.__init__(self) + + def CreateControls(self, propgrid, property, pos, sz): + try: + h = 64 + 6 + x = propgrid.GetSplitterPosition() + x2 = propgrid.GetClientSize().x + bw = propgrid.GetRowHeight() + lipc = LargeImagePickerCtrl() + if sys.platform == 'win32': + lipc.Hide() + lipc.Create(propgrid, wxpg.PG_SUBID1, (x,pos[1]), (x2-x-bw,h)) + lipc.SetProperty(property) + # Hmmm.. how to have two-stage creation without subclassing? + #btn = wx.PreButton() + #pre = wx.PreWindow() + #self.PostCreate(pre) + #if sys.platform == 'win32': + # btn.Hide() + #btn.Create(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS) + btn = wx.Button(propgrid, wxpg.PG_SUBID2, '...', (x2-bw,pos[1]), (bw,h), wx.WANTS_CHARS) + return (lipc, btn) + except: + import traceback + print traceback.print_exc() + + def UpdateControl(self, property, ctrl): + ctrl.SetValue(property.GetDisplayedString()) + + def DrawValue(self, dc, property, rect): + if not (property.GetFlags() & wxpg.PG_PROP_UNSPECIFIED): + dc.DrawText( property.GetDisplayedString(), rect.x+5, rect.y ); + + def OnEvent(self, propgrid, ctrl, event): + if not ctrl: + return False + + evtType = event.GetEventType() + + if evtType == wx.wxEVT_COMMAND_TEXT_ENTER: + if propgrid.IsEditorsValueModified(): + return True + + elif evtType == wx.wxEVT_COMMAND_TEXT_UPDATED: + if not property.HasFlag(wxpg.PG_PROP_UNSPECIFIED) or not ctrl or \ + ctrl.GetLastPosition() > 0: + + # We must check this since an 'empty' text event + # may be triggered when creating the property. + PG_FL_IN_SELECT_PROPERTY = 0x00100000 + if not (propgrid.GetInternalFlags() & PG_FL_IN_SELECT_PROPERTY): + event.Skip(); + event.SetId(propGrid.GetId()); + + propgrid.EditorsValueWasModified(); + + return False + + + def CopyValueFromControl(self, property, ctrl): + tc = ctrl.tc + res = property.SetValueFromString(tc.GetValue(),0) + # Changing unspecified always causes event (returning + # true here should be enough to trigger it). + if not res and property.IsFlagSet(wxpg.PG_PROP_UNSPECIFIED): + res = true + + return res + + def SetValueToUnspecified(self, ctrl): + ctrl.tc.Remove(0,len(ctrl.tc.GetValue())); + + def SetControlStringValue(self, ctrl, txt): + ctrl.SetValue(txt) + + def OnFocus(self, property, ctrl): + ctrl.tc.SetSelection(-1,-1) + ctrl.tc.SetFocus() + + +class PropertyEditor(wx.Panel): + + def __init__(self, parent): + # Use the WANTS_CHARS style so the panel doesn't eat the Return key. + wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(160, 200)) + + sizer = wx.BoxSizer(wx.VERTICAL) + + self.pg = wxpg.PropertyGrid(self, style=wxpg.PG_SPLITTER_AUTO_CENTER|wxpg.PG_AUTO_SORT) + + # Show help as tooltips + self.pg.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS) + + #pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChange) + #pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect) + #self.pg.Bind(wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick) + + # Needed by custom image editor + wx.InitAllImageHandlers() + + # + # Let's create a simple custom editor + # + # NOTE: Editor must be registered *before* adding a property that uses it. + self.pg.RegisterEditor(LargeImageEditor) + + ''' + # + # Add properties + # + + pg.Append( wxpg.PropertyCategory("1 - Basic Properties") ) + pg.Append( wxpg.StringProperty("String",value="Some Text") ) + pg.Append( wxpg.IntProperty("Int",value=100) ) + pg.Append( wxpg.FloatProperty("Float",value=100.0) ) + pg.Append( wxpg.BoolProperty("Bool",value=True) ) + pg.Append( wxpg.BoolProperty("Bool_with_Checkbox",value=True) ) + pg.SetPropertyAttribute("Bool_with_Checkbox", "UseCheckbox", True) + + pg.Append( wxpg.PropertyCategory("2 - More Properties") ) + pg.Append( wxpg.LongStringProperty("LongString",value="This is a\\nmulti-line string\\nwith\\ttabs\\nmixed\\tin.") ) + pg.Append( wxpg.DirProperty("Dir",value="C:\\Windows") ) + pg.Append( wxpg.FileProperty("File",value="C:\\Windows\\system.ini") ) + pg.Append( wxpg.ArrayStringProperty("ArrayString",value=['A','B','C']) ) + + pg.Append( wxpg.EnumProperty("Enum","Enum", + ['wxPython Rules','wxPython Rocks','wxPython Is The Best'], + [10,11,12],0) ) + pg.Append( wxpg.EditEnumProperty("EditEnum","EditEnumProperty",['A','B','C'],[0,1,2],"Text Not in List") ) + + pg.Append( wxpg.PropertyCategory("3 - Advanced Properties") ) + pg.Append( wxpg.DateProperty("Date",value=wx.DateTime_Now()) ) + pg.Append( wxpg.FontProperty("Font",value=self.GetFont()) ) + pg.Append( wxpg.ColourProperty("Colour",value=self.GetBackgroundColour()) ) + pg.Append( wxpg.SystemColourProperty("SystemColour") ) + pg.Append( wxpg.ImageFileProperty("ImageFile") ) + pg.Append( wxpg.MultiChoiceProperty("MultiChoice",choices=['wxWidgets','QT','GTK+']) ) + + pg.Append( wxpg.PropertyCategory("4 - Additional Properties") ) + pg.Append( wxpg.PointProperty("Point",value=self.GetPosition()) ) + pg.Append( wxpg.SizeProperty("Size",value=self.GetSize()) ) + pg.Append( wxpg.FontDataProperty("FontData") ) + pg.Append( wxpg.IntProperty("IntWithSpin",value=256) ) + pg.SetPropertyEditor("IntWithSpin","SpinCtrl") + pg.Append( wxpg.DirsProperty("Dirs",value=['C:/Lib','C:/Bin']) ) + pg.SetPropertyHelpString( "String", "String Property help string!" ) + pg.SetPropertyHelpString( "Dirs", "Dirs Property help string!" ) + + pg.SetPropertyAttribute( "File", wxpg.PG_FILE_SHOW_FULL_PATH, 0 ) + pg.SetPropertyAttribute( "File", wxpg.PG_FILE_INITIAL_PATH, "C:\\Program Files\\Internet Explorer" ) + pg.SetPropertyAttribute( "Date", wxpg.PG_DATE_PICKER_STYLE, wx.DP_DROPDOWN|wx.DP_SHOWCENTURY ) + + pg.Append( wxpg.PropertyCategory("5 - Custom Properties") ) + pg.Append( IntProperty2("IntProperty2", value=1024) ) + + pg.Append( ShapeProperty("ShapeProperty", value=0) ) + pg.Append( PyObjectProperty("PyObjectProperty") ) + + pg.Append( wxpg.ImageFileProperty("ImageFileWithLargeEditor") ) + pg.SetPropertyEditor("ImageFileWithLargeEditor", "LargeImageEditor") + + + pg.SetPropertyClientData( "Point", 1234 ) + if pg.GetPropertyClientData( "Point" ) != 1234: + raise ValueError("Set/GetPropertyClientData() failed") + + # Test setting unicode string + pg.GetPropertyByName("String").SetValue(u"Some Unicode Text") + + # + # Test some code that *should* fail (but not crash) + #try: + #a_ = pg.GetPropertyValue( "NotARealProperty" ) + #pg.EnableProperty( "NotAtAllRealProperty", False ) + #pg.SetPropertyHelpString( "AgaintNotARealProperty", "Dummy Help String" ) + #except: + #pass + #raise + + ''' + sizer.Add(self.pg, 1, wx.EXPAND) + self.SetSizer(sizer) + sizer.SetSizeHints(self) + + self.SelectedTreeItem = None + + def GetPropertyValues(self): + #return self.pg.GetPropertyValues(as_strings=True) + return self.pg.GetPropertyValues() + + + def Initialize(self, properties): + pg = self.pg + pg.Clear() + + if properties: + for element in properties: + if element[1]['type'] == 'integer': + if 'value' in element[1]: + property_value = element[1].as_int('value') + else: + property_value = element[1].as_int('default') + property_control = wxpg.IntProperty(element[0], value=property_value) + if 'maximum' in element[1]: + property_control.SetAttribute('Max', element[1].as_int('maximum')) + if 'minimum' in element[1]: + property_control.SetAttribute('Min', element[1].as_int('minimum')) + property_control.SetAttribute('Wrap', True) + pg.Append(property_control) + pg.SetPropertyEditor(element[0], 'SpinCtrl') + + if element[1]['type'] == 'float': + if 'value' in element[1]: + property_value = element[1].as_float('value') + else: + property_value = element[1].as_float('default') + property_control = wxpg.FloatProperty(element[0], value=property_value) + if 'maximum' in element[1]: + property_control.SetAttribute('Max', element[1].as_float('maximum')) + if 'minimum' in element[1]: + property_control.SetAttribute('Min', element[1].as_float('minimum')) + property_control.SetAttribute('Wrap', True) + pg.Append(property_control) + pg.SetPropertyEditor(element[0], 'SpinCtrl') + + if element[1]['type'] == 'boolean': + if 'value' in element[1]: + property_value = element[1].as_bool('value') + else: + property_value = element[1].as_bool('default') + property_control = wxpg.BoolProperty(element[0], value=property_value) + pg.Append(property_control) + pg.SetPropertyAttribute(element[0], 'UseCheckbox', True) + if element[1]['type'] == 'string': + if 'value' in element[1]: + property_value = element[1]['value'] + else: + property_value = element[1]['default'] + pg.Append(wxpg.StringProperty(element[0], value=property_value)) + if element[1]['type'] == 'folder': + if 'value' in element[1]: + property_value = element[1]['value'] + else: + property_value = element[1]['default'] + pg.Append(wxpg.DirProperty(element[0], value=property_value)) + if element[1]['type'] == 'filename': + if 'value' in element[1]: + property_value = element[1]['value'] + else: + property_value = element[1]['default'] + pg.Append(wxpg.FileProperty(element[0], value=property_value)) + #if element[0] == 'category': + #pg.Append(wxpg.PropertyCategory(element[1])) + #if element[0] == 'folder': + #pg.Append(wxpg.DirProperty(element[1], value=element[2])) + #if element[0] == 'string': + #pg.Append(wxpg.StringProperty(element[1], value=element[2])) + + pg.Refresh() + + def OnReserved(self, event): + pass + + +#--------------------------------------------------------------------------- + + +class MemoDialog(wx.Dialog): + """\ + Dialog for multi-line text editing. + """ + def __init__(self, parent=None, title='', text='', pos=None, size=(500,500)): + wx.Dialog.__init__(self, parent, -1, title, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) + + sizer = wx.BoxSizer(wx.VERTICAL) + + tc = wx.TextCtrl(self, 11, text, style=wx.TE_MULTILINE) + self.tc = tc + topsizer.Add(tc,1,wx.EXPAND|wx.ALL,8) + + rowsizer = wx.BoxSizer( wx.HORIZONTAL ) + rowsizer.Add(wx.Button(self,wx.ID_OK,'Ok'),0,wx.ALIGN_RIGHT|wx.ALIGN_CENTRE_VERTICAL,8) + rowsizer.Add((0,0),1,wx.ALIGN_RIGHT|wx.ALIGN_CENTRE_VERTICAL,8) + rowsizer.Add(wx.Button(self,wx.ID_CANCEL,'Cancel'),0,wx.ALIGN_RIGHT|wx.ALIGN_CENTRE_VERTICAL,8) + topsizer.Add(rowsizer,0,wx.EXPAND|wx.ALL,8) + + self.SetSizer( topsizer ) + topsizer.Layout() + + self.SetSize( size ) + if not pos: + self.CenterOnScreen() + else: + self.Move(pos) + + +#--------------------------------------------------------------------------- diff --git a/hooke/ui/gui/hookeresults.py b/hooke/ui/gui/hookeresults.py new file mode 100644 index 0000000..b3f10c9 --- /dev/null +++ b/hooke/ui/gui/hookeresults.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python + +import sys +import wx +from wx.lib.mixins.listctrl import CheckListCtrlMixin + +import prettyformat + +class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin): + def __init__(self, parent): + wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT) + CheckListCtrlMixin.__init__(self) + self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated) + + def OnItemActivated(self, evt): + self.ToggleItem(evt.m_itemIndex) + + +class Results(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, -1) + self.results_list = CheckListCtrl(self) + sizer = wx.BoxSizer() + sizer.Add(self.results_list, 1, wx.EXPAND) + self.SetSizer(sizer) + self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.results_list) + self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self.results_list) + #self.results_list.OnCheckItem = self.OnCheckItem + + def _GetWidthInPixels(self, text): + #TODO: + #Returns the width of a string in pixels + #Unfortunately, it does not work terribly well (although it should). + #Thus, we have to add a bit afterwards. + #Annoys the heck out of me. + font = self.results_list.GetFont() + dc = wx.WindowDC(self.results_list) + dc.SetFont(font) + width, height = dc.GetTextExtent(text) + return width + + def ClearResults(self): + self.results_list.ClearAll() + + def DisplayResults(self, results): + self.ClearResults() + header = results.header_as_list() + self.results_list.InsertColumn(0, 'Show') + for index, column in enumerate(header): + self.results_list.InsertColumn(index + 1, column, wx.LIST_FORMAT_RIGHT) + + for result in results.results: + done = False + for index, column in enumerate(results.columns): + value_str = results.get_pretty_value(column, result.result[column]) + if not done: + index_col = self.results_list.InsertStringItem(sys.maxint, '') + done = True + column_width = len(self.results_list.GetColumn(index + 1).GetText()) + value_str = value_str.center(column_width) + self.results_list.SetStringItem(index_col, index + 1, value_str) + #self.results_list.SetItemData(index, result) + + for index, result in enumerate(results.results): + if result.visible: + #if we use 'CheckItem' then 'UpdatePlot' is called (ie repeated updates) + self.results_list.SetItemImage(index, 1) + #self.results_list.CheckItem(index) + for index in range(self.results_list.GetColumnCount()): + column_text = self.results_list.GetColumn(index).GetText() + column_width = self._GetWidthInPixels(column_text) + self.results_list.SetColumnWidth(index, column_width + 15) + + def OnItemSelected(self, evt): + pass + + def OnItemDeselected(self, evt): + pass diff --git a/hooke/ui/gui/perspectives/Default.txt b/hooke/ui/gui/perspectives/Default.txt new file mode 100644 index 0000000..074a4ca --- /dev/null +++ b/hooke/ui/gui/perspectives/Default.txt @@ -0,0 +1 @@ +layout2|name=Folders;caption=Folders;state=33818620;dir=4;layer=0;row=0;pos=0;prop=100000;bestw=196;besth=246;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=Playlists;caption=Playlists;state=33818620;dir=4;layer=0;row=0;pos=1;prop=100000;bestw=160;besth=250;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=Plots;caption=;state=256;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=430;besth=200;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=Commands;caption=Settings and commands;state=33818620;dir=2;layer=0;row=0;pos=0;prop=100000;bestw=160;besth=273;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=Properties;caption=Properties;state=33818620;dir=2;layer=0;row=0;pos=1;prop=100000;bestw=60;besth=58;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=Assistant;caption=Assistant;state=33818620;dir=2;layer=0;row=0;pos=2;prop=100000;bestw=133;besth=90;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=Output;caption=Output;state=33818620;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=133;besth=90;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=Results;caption=Results;state=33818620;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=20;besth=20;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=toolbar;caption=Toolbar;state=33827568;dir=1;layer=1;row=1;pos=0;prop=100000;bestw=100;besth=23;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=toolbarNavigation;caption=Navigation;state=33827568;dir=1;layer=1;row=1;pos=111;prop=100000;bestw=46;besth=23;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(4,0,0)=198|dock_size(5,0,0)=325|dock_size(2,0,0)=162|dock_size(3,0,0)=111|dock_size(1,1,1)=25| \ No newline at end of file diff --git a/hooke/ui/gui/prettyformat.py b/hooke/ui/gui/prettyformat.py new file mode 100644 index 0000000..6454ad9 --- /dev/null +++ b/hooke/ui/gui/prettyformat.py @@ -0,0 +1,141 @@ +''' +// +// prettyformat.py - simple Python function to format values with nice prefixes +// Version 1.0.1 +// +// History +// 2009 07 16: added negative number support +// added decimal-formatted output +// +// Copyright (c) 2009 Rolf Schmidt, Montreal +// rschmidt@alcor.concordia.ca +// +// This procedure is released under the GNU General Public License version 2 +// +''' + +import math +from numpy import isnan + +def pretty_format(fValue, sUnit='', iDecimals=-1, iMultiplier=1, bLeadingSpaces=False): + if fValue != 0: + iLeadingSpaces = 0 + if bLeadingSpaces: + iLeadingSpaces = 5 + if iMultiplier == 1: + iMultiplier=get_multiplier(fValue) + sUnitString = '' + if sUnit != '': + sUnitString = ' ' + get_prefix(iMultiplier) + sUnit + if iDecimals >= 0: + formatString = '% ' + repr(iLeadingSpaces + iDecimals) + '.' + repr(iDecimals) + 'f' + return formatString % (fValue / iMultiplier) + sUnitString + else: + return str(fValue / iMultiplier) + sUnitString + else: + return '0' + return str(fValue / iMultiplier) + ' ' + get_prefix(fValue / iMultiplier) + sUnit + +def get_multiplier(fValue): + return pow(10, get_power(fValue)) + +def get_power(fValue): + if fValue != 0 and not isnan(fValue): + #get the log10 from fValue (make sure the value is not negative) + dHelp = math.floor(math.log10(math.fabs(fValue))) + #reduce the log10 to a multiple of 3 and return it + return dHelp-(dHelp % 3) + else: + return 0 + +def get_prefix(fValue): + #set up a dictionary to find the prefix + prefix = { + 24: lambda: 'Y', + 21: lambda: 'Z', + 18: lambda: 'E', + 15: lambda: 'P', + 12: lambda: 'T', + 9: lambda: 'G', + 6: lambda: 'M', + 3: lambda: 'k', + 0: lambda: '', + -3: lambda: 'm', + -6: lambda: u'\u00B5', + -9: lambda: 'n', + -12: lambda: 'p', + -15: lambda: 'f', + -18: lambda: 'a', + -21: lambda: 'z', + -24: lambda: 'y', + } + if fValue != 0 and not isnan(fValue): + #get the log10 from fValue + dHelp = math.floor(math.log10(math.fabs(fValue))) + else: + dHelp = 0 + #reduce the log10 to a multiple of 3 and create the return string + return prefix.get(dHelp - (dHelp % 3))() + +''' +dTestValue=-2.4115665714484597e-008 +print 'Value: '+str(dTestValue)+')' +print 'pretty_format example (value, unit)' +print pretty_format(dTestValue, 'N') +print'-----------------------' +print 'pretty_format example (value, unit, decimals)' +print pretty_format(dTestValue, 'N', 3) +print'-----------------------' +print 'pretty_format example (value, unit, decimals, multiplier)' +print pretty_format(dTestValue, 'N', 5, 0.000001) +print'-----------------------' +print 'pretty_format example (value, unit, decimals, multiplier, leading spaces)' +print pretty_format(0.0166276297705, 'N', 3, 0.001, True) +print pretty_format(0.00750520813323, 'N', 3, 0.001, True) +print pretty_format(0.0136453282825, 'N', 3, 0.001, True) +''' +''' +#example use autoFormatValue +dTestValue=0.00000000567 +print 'autoFormatValue example ('+str(dTestValue)+')' +print autoFormatValue(dTestValue, 'N') +#outputs 5.67 nN +''' +''' +#example use of decimalFormatValue(fValue, iDecimals, sUnit): +dTestValue=-2.4115665714484597e-008 +iDecimals=3 +print 'decimalFormatValue example ('+str(dTestValue)+')' +print decimalFormatValue(dTestValue, iDecimals, 'N') +#outputs -24.116 nN +#change iDecimals to see the effect +''' +''' +#example use formatValue +dTestValue=0.000000000567 +print 'formatValue example ('+str(dTestValue)+')' +#find the (common) multiplier +iMultiplier=get_multiplier(dTestValue) +#use the multiplier and a unit to format the value +print formatValue(dTestValue, iMultiplier, 'N') +#outputs 567.0 pN +''' +''' +#to output a scale: +#choose any value on the axis and find the multiplier and prefix for it +#use those to format the rest of the scale +#as values can span several orders of magnitude, you have to decide what units to use + +#tuple of values: +scaleValues=0.000000000985, 0.000000001000, 0.000000001015 +#use this element (change to 1 or 2 to see the effect on the scale and label) +iIndex=0 +#get the multiplier from the value at iIndex +iMultiplier=get_multiplier(scaleValues[iIndex]) +print '\nScale example' +iDecimals=3 +#print the scale +for aValue in scaleValues: print decimalFormat(aValue/iMultiplier, iDecimals), +#print the scale label using the value at iIndex +print '\n'+get_prefix(scaleValues[iIndex])+'N' +''' \ No newline at end of file diff --git a/hooke/ui/gui/results.py b/hooke/ui/gui/results.py new file mode 100644 index 0000000..48c549c --- /dev/null +++ b/hooke/ui/gui/results.py @@ -0,0 +1,150 @@ +import prettyformat + +DEFAULT_COLOR = 'orange' +DEFAULT_DECIMAL = 2 +DEFAULT_STYLE = 'plot' + +class Result: + def __init__(self): + self.color = DEFAULT_COLOR + self.result = {} + self.style = DEFAULT_STYLE + self.visible = True + self.x = [] + self.y = [] + +class Results: + def __init__(self): + self.columns = [] + self.decimals = {} + self.has_multipliers = False + self.multipliers = {} + self.results = [] + self.separator='\t' + self.units = {} + + def get_pretty_value(self, column, value): + if self.has_multipliers and self.has_results(): + multiplier = self.multipliers[column] + decimals = self.decimals[column] + return prettyformat.pretty_format(value, '', decimals, multiplier, True) + return str(value) + + #def get_fit_result(self): + #if not(self.has_multipliers): + #self.set_multipliers() + + #sResult = 'Contour length ['+prettyformat.get_prefix(self.multiplierContourLength) + 'm]' + self.separator + #sResult += prettyformat.pretty_format(self.contourLength[0], '', self.decimals, self.multiplierContourLength, True) + '\n' + #sResult += 'Persistence length ['+prettyformat.get_prefix(self.multiplierPersistenceLength) + 'm]' + self.separator + #sResult += prettyformat.pretty_format(self.persistenceLength[0], '', self.decimals, self.multiplierPersistenceLength, True) + '\n' + #sResult += 'Rupture force ['+prettyformat.get_prefix(self.multiplierRuptureForce) + 'N]' + self.separator + #sResult += prettyformat.pretty_format(self.ruptureForces[0], '', self.decimals, self.multiplierRuptureForce, True) + '\n' + #sResult += 'Loading rate ['+prettyformat.get_prefix(self.multiplierSlope) + 'N/m]' + self.separator + #sResult += prettyformat.pretty_format(self.slopes[0], '', self.decimals, self.multiplierSlope, True)+'\n' + #sResult += 'Sigma contour ['+prettyformat.get_prefix(self.multiplierContourLength) + 'm]' + self.separator + #sResult += prettyformat.pretty_format(self.contourLengthSigma[0], '', self.decimals, self.multiplierContourLength, True) + '\n' + #sResult += 'Sigma persistence ['+prettyformat.get_prefix(self.multiplierPersistenceLength) + 'm]' + self.separator + #sResult += prettyformat.pretty_format(self.persistenceLengthSigma[0], '', self.decimals, self.multiplierPersistenceLength, True) + + #return sResult + + #def get_fit_results(self, index): + #if index >= 0 and index < len(self.contourLength): + #if not(self.has_multipliers): + #self.set_multipliers() + #sLine = prettyformat.pretty_format(self.contourLength[index], '', self.decimals, self.multiplierContourLength, True) + self.separator + #sLine += prettyformat.pretty_format(self.persistenceLength[index], '', self.decimals, self.multiplierPersistenceLength, True) + self.separator + #sLine += prettyformat.pretty_format(self.ruptureForces[index], '', self.decimals, self.multiplierRuptureForce, True) + self.separator + #sLine += prettyformat.pretty_format(self.slopes[index], '', self.decimals, self.multiplierSlope, True) + self.separator + #sLine += prettyformat.pretty_format(self.contourLengthSigma[index], '', self.decimals, self.multiplierContourLength, True) + self.separator + #sLine += prettyformat.pretty_format(self.persistenceLengthSigma[index], '', self.decimals, self.multiplierPersistenceLength, True) + + #return sLine + #else: + #return '' + + def has_results(self): + return self.results + + def header_as_list(self): + header = [] + if self.has_results(): + if not self.has_multipliers: + self.set_multipliers() + for column in self.columns: + #result will contain the results dictionary for 'column' + #result = self.results[0][0][column] + #result[1] contains the unit + unit_str = ''.join([prettyformat.get_prefix(self.multipliers[column]), self.units[column]]) + header_str = ''.join([column, ' [', unit_str, ']']) + header.append(header_str) + return header + + #def header_as_str(self): + #if self.has_results(): + #if not self.has_multipliers: + #self.set_multipliers() + #header_str = '' + #for column in self.columns: + ##result will contain the results dictionary for 'column' + #result = self.results[0][0][column] + ##result[1] contains the unit + #unit_str = ''.join([prettyformat.get_prefix(self.multipliers[column]), result[1]]) + #header_str = ''.join([header_str, result_str, ' [', unit_str, ']', self.separator]) + #return header_str + #else: + #return None + + def set_decimal(self, column, decimal=DEFAULT_DECIMAL): + if self.decimals.has_key(name): + self.decimals[name] = decimal + + def set_decimals(self, decimals=DEFAULT_DECIMAL): + if decimals < 0: + #set default value if necessary + decimals = DEFAULT_DECIMAL + for column in self.columns: + self.decimals[column] = decimals + + def set_multipliers(self, index=0): + if self.has_results(): + if index >= 0 and index < len(self.results): + for column in self.columns: + #result will contain the results dictionary at 'index' + result = self.results[index][0] + #in position 0 of the result we find the value + self.multipliers[column] = prettyformat.get_multiplier(result[column][0]) + self.has_multipliers = True + else: + self.has_multipliers = False + + +class ResultsWLC(Results): + def __init__(self): + Results.__init__(self) + self.columns = ['Contour length', 'sigma contour length', 'Persistence length', 'sigma persistence length', 'Rupture force', 'Loading rate'] + self.units['Contour length'] = 'm' + self.units['sigma contour length'] = 'm' + self.units['Persistence length'] = 'm' + self.units['sigma persistence length'] = 'm' + self.units['Rupture force'] = 'N' + self.units['Loading rate'] = 'N/m' + self.set_decimals(2) + + def set_multipliers(self, index=0): + if self.has_results(): + if index >= 0 and index < len(self.results): + for column in self.columns: + #result will contain the results dictionary at 'index' + result = self.results[index].result + #in position 0 of the result we find the value + if column == 'sigma contour length': + self.multipliers[column] = self.multipliers['Contour length'] + elif column == 'sigma persistence length': + self.multipliers[column] = self.multipliers['Persistence length'] + else: + self.multipliers[column] = prettyformat.get_multiplier(result[column]) + self.has_multipliers = True + else: + self.has_multipliers = False -- 2.26.2